1package hcsshim
2
3import (
4	"context"
5	"fmt"
6	"os"
7	"sync"
8	"time"
9
10	"github.com/Microsoft/hcsshim/internal/hcs"
11	"github.com/Microsoft/hcsshim/internal/mergemaps"
12	"github.com/Microsoft/hcsshim/internal/schema1"
13)
14
15// ContainerProperties holds the properties for a container and the processes running in that container
16type ContainerProperties = schema1.ContainerProperties
17
18// MemoryStats holds the memory statistics for a container
19type MemoryStats = schema1.MemoryStats
20
21// ProcessorStats holds the processor statistics for a container
22type ProcessorStats = schema1.ProcessorStats
23
24// StorageStats holds the storage statistics for a container
25type StorageStats = schema1.StorageStats
26
27// NetworkStats holds the network statistics for a container
28type NetworkStats = schema1.NetworkStats
29
30// Statistics is the structure returned by a statistics call on a container
31type Statistics = schema1.Statistics
32
33// ProcessList is the structure of an item returned by a ProcessList call on a container
34type ProcessListItem = schema1.ProcessListItem
35
36// MappedVirtualDiskController is the structure of an item returned by a MappedVirtualDiskList call on a container
37type MappedVirtualDiskController = schema1.MappedVirtualDiskController
38
39// Type of Request Support in ModifySystem
40type RequestType = schema1.RequestType
41
42// Type of Resource Support in ModifySystem
43type ResourceType = schema1.ResourceType
44
45// RequestType const
46const (
47	Add     = schema1.Add
48	Remove  = schema1.Remove
49	Network = schema1.Network
50)
51
52// ResourceModificationRequestResponse is the structure used to send request to the container to modify the system
53// Supported resource types are Network and Request Types are Add/Remove
54type ResourceModificationRequestResponse = schema1.ResourceModificationRequestResponse
55
56type container struct {
57	system   *hcs.System
58	waitOnce sync.Once
59	waitErr  error
60	waitCh   chan struct{}
61}
62
63// createComputeSystemAdditionalJSON is read from the environment at initialisation
64// time. It allows an environment variable to define additional JSON which
65// is merged in the CreateComputeSystem call to HCS.
66var createContainerAdditionalJSON []byte
67
68func init() {
69	createContainerAdditionalJSON = ([]byte)(os.Getenv("HCSSHIM_CREATECONTAINER_ADDITIONALJSON"))
70}
71
72// CreateContainer creates a new container with the given configuration but does not start it.
73func CreateContainer(id string, c *ContainerConfig) (Container, error) {
74	fullConfig, err := mergemaps.MergeJSON(c, createContainerAdditionalJSON)
75	if err != nil {
76		return nil, fmt.Errorf("failed to merge additional JSON '%s': %s", createContainerAdditionalJSON, err)
77	}
78
79	system, err := hcs.CreateComputeSystem(context.Background(), id, fullConfig)
80	if err != nil {
81		return nil, err
82	}
83	return &container{system: system}, err
84}
85
86// OpenContainer opens an existing container by ID.
87func OpenContainer(id string) (Container, error) {
88	system, err := hcs.OpenComputeSystem(context.Background(), id)
89	if err != nil {
90		return nil, err
91	}
92	return &container{system: system}, err
93}
94
95// GetContainers gets a list of the containers on the system that match the query
96func GetContainers(q ComputeSystemQuery) ([]ContainerProperties, error) {
97	return hcs.GetComputeSystems(context.Background(), q)
98}
99
100// Start synchronously starts the container.
101func (container *container) Start() error {
102	return convertSystemError(container.system.Start(context.Background()), container)
103}
104
105// Shutdown requests a container shutdown, but it may not actually be shutdown until Wait() succeeds.
106func (container *container) Shutdown() error {
107	err := container.system.Shutdown(context.Background())
108	if err != nil {
109		return convertSystemError(err, container)
110	}
111	return &ContainerError{Container: container, Err: ErrVmcomputeOperationPending, Operation: "hcsshim::ComputeSystem::Shutdown"}
112}
113
114// Terminate requests a container terminate, but it may not actually be terminated until Wait() succeeds.
115func (container *container) Terminate() error {
116	err := container.system.Terminate(context.Background())
117	if err != nil {
118		return convertSystemError(err, container)
119	}
120	return &ContainerError{Container: container, Err: ErrVmcomputeOperationPending, Operation: "hcsshim::ComputeSystem::Terminate"}
121}
122
123// Waits synchronously waits for the container to shutdown or terminate.
124func (container *container) Wait() error {
125	err := container.system.Wait()
126	if err == nil {
127		err = container.system.ExitError()
128	}
129	return convertSystemError(err, container)
130}
131
132// WaitTimeout synchronously waits for the container to terminate or the duration to elapse. It
133// returns false if timeout occurs.
134func (container *container) WaitTimeout(timeout time.Duration) error {
135	container.waitOnce.Do(func() {
136		container.waitCh = make(chan struct{})
137		go func() {
138			container.waitErr = container.Wait()
139			close(container.waitCh)
140		}()
141	})
142	t := time.NewTimer(timeout)
143	defer t.Stop()
144	select {
145	case <-t.C:
146		return &ContainerError{Container: container, Err: ErrTimeout, Operation: "hcsshim::ComputeSystem::Wait"}
147	case <-container.waitCh:
148		return container.waitErr
149	}
150}
151
152// Pause pauses the execution of a container.
153func (container *container) Pause() error {
154	return convertSystemError(container.system.Pause(context.Background()), container)
155}
156
157// Resume resumes the execution of a container.
158func (container *container) Resume() error {
159	return convertSystemError(container.system.Resume(context.Background()), container)
160}
161
162// HasPendingUpdates returns true if the container has updates pending to install
163func (container *container) HasPendingUpdates() (bool, error) {
164	return false, nil
165}
166
167// Statistics returns statistics for the container. This is a legacy v1 call
168func (container *container) Statistics() (Statistics, error) {
169	properties, err := container.system.Properties(context.Background(), schema1.PropertyTypeStatistics)
170	if err != nil {
171		return Statistics{}, convertSystemError(err, container)
172	}
173
174	return properties.Statistics, nil
175}
176
177// ProcessList returns an array of ProcessListItems for the container. This is a legacy v1 call
178func (container *container) ProcessList() ([]ProcessListItem, error) {
179	properties, err := container.system.Properties(context.Background(), schema1.PropertyTypeProcessList)
180	if err != nil {
181		return nil, convertSystemError(err, container)
182	}
183
184	return properties.ProcessList, nil
185}
186
187// This is a legacy v1 call
188func (container *container) MappedVirtualDisks() (map[int]MappedVirtualDiskController, error) {
189	properties, err := container.system.Properties(context.Background(), schema1.PropertyTypeMappedVirtualDisk)
190	if err != nil {
191		return nil, convertSystemError(err, container)
192	}
193
194	return properties.MappedVirtualDiskControllers, nil
195}
196
197// CreateProcess launches a new process within the container.
198func (container *container) CreateProcess(c *ProcessConfig) (Process, error) {
199	p, err := container.system.CreateProcess(context.Background(), c)
200	if err != nil {
201		return nil, convertSystemError(err, container)
202	}
203	return &process{p: p.(*hcs.Process)}, nil
204}
205
206// OpenProcess gets an interface to an existing process within the container.
207func (container *container) OpenProcess(pid int) (Process, error) {
208	p, err := container.system.OpenProcess(context.Background(), pid)
209	if err != nil {
210		return nil, convertSystemError(err, container)
211	}
212	return &process{p: p}, nil
213}
214
215// Close cleans up any state associated with the container but does not terminate or wait for it.
216func (container *container) Close() error {
217	return convertSystemError(container.system.Close(), container)
218}
219
220// Modify the System
221func (container *container) Modify(config *ResourceModificationRequestResponse) error {
222	return convertSystemError(container.system.Modify(context.Background(), config), container)
223}
224