diff options
Diffstat (limited to 'vendor/github.com/Microsoft/hcsshim/internal/hcs/system.go')
-rw-r--r-- | vendor/github.com/Microsoft/hcsshim/internal/hcs/system.go | 667 |
1 files changed, 667 insertions, 0 deletions
diff --git a/vendor/github.com/Microsoft/hcsshim/internal/hcs/system.go b/vendor/github.com/Microsoft/hcsshim/internal/hcs/system.go new file mode 100644 index 000000000..2629380fd --- /dev/null +++ b/vendor/github.com/Microsoft/hcsshim/internal/hcs/system.go @@ -0,0 +1,667 @@ +package hcs + +import ( + "encoding/json" + "os" + "strconv" + "sync" + "syscall" + "time" + + "github.com/Microsoft/hcsshim/internal/interop" + "github.com/Microsoft/hcsshim/internal/logfields" + "github.com/Microsoft/hcsshim/internal/schema1" + "github.com/Microsoft/hcsshim/internal/timeout" + "github.com/sirupsen/logrus" +) + +// currentContainerStarts is used to limit the number of concurrent container +// starts. +var currentContainerStarts containerStarts + +type containerStarts struct { + maxParallel int + inProgress int + sync.Mutex +} + +func init() { + mpsS := os.Getenv("HCSSHIM_MAX_PARALLEL_START") + if len(mpsS) > 0 { + mpsI, err := strconv.Atoi(mpsS) + if err != nil || mpsI < 0 { + return + } + currentContainerStarts.maxParallel = mpsI + } +} + +type System struct { + handleLock sync.RWMutex + handle hcsSystem + id string + callbackNumber uintptr + + logctx logrus.Fields +} + +func newSystem(id string) *System { + return &System{ + id: id, + logctx: logrus.Fields{ + logfields.HCSOperation: "", + logfields.ContainerID: id, + }, + } +} + +func (computeSystem *System) logOperationBegin(operation string) { + computeSystem.logctx[logfields.HCSOperation] = operation + logOperationBegin( + computeSystem.logctx, + "hcsshim::ComputeSystem - Begin Operation") +} + +func (computeSystem *System) logOperationEnd(err error) { + var result string + if err == nil { + result = "Success" + } else { + result = "Error" + } + + logOperationEnd( + computeSystem.logctx, + "hcsshim::ComputeSystem - End Operation - "+result, + err) + computeSystem.logctx[logfields.HCSOperation] = "" +} + +// CreateComputeSystem creates a new compute system with the given configuration but does not start it. +func CreateComputeSystem(id string, hcsDocumentInterface interface{}) (_ *System, err error) { + operation := "hcsshim::CreateComputeSystem" + + computeSystem := newSystem(id) + computeSystem.logOperationBegin(operation) + defer computeSystem.logOperationEnd(err) + + hcsDocumentB, err := json.Marshal(hcsDocumentInterface) + if err != nil { + return nil, err + } + + hcsDocument := string(hcsDocumentB) + + logrus.WithFields(computeSystem.logctx). + WithField(logfields.JSON, hcsDocument). + Debug("HCS ComputeSystem Document") + + var ( + resultp *uint16 + identity syscall.Handle + ) + completed := false + go syscallWatcher(computeSystem.logctx, &completed) + createError := hcsCreateComputeSystem(id, hcsDocument, identity, &computeSystem.handle, &resultp) + completed = true + + if createError == nil || IsPending(createError) { + if err = computeSystem.registerCallback(); err != nil { + // Terminate the compute system if it still exists. We're okay to + // ignore a failure here. + computeSystem.Terminate() + return nil, makeSystemError(computeSystem, operation, "", err, nil) + } + } + + events, err := processAsyncHcsResult(createError, resultp, computeSystem.callbackNumber, hcsNotificationSystemCreateCompleted, &timeout.SystemCreate) + if err != nil { + if err == ErrTimeout { + // Terminate the compute system if it still exists. We're okay to + // ignore a failure here. + computeSystem.Terminate() + } + return nil, makeSystemError(computeSystem, operation, hcsDocument, err, events) + } + + return computeSystem, nil +} + +// OpenComputeSystem opens an existing compute system by ID. +func OpenComputeSystem(id string) (_ *System, err error) { + operation := "hcsshim::OpenComputeSystem" + + computeSystem := newSystem(id) + computeSystem.logOperationBegin(operation) + defer computeSystem.logOperationEnd(err) + + var ( + handle hcsSystem + resultp *uint16 + ) + err = hcsOpenComputeSystem(id, &handle, &resultp) + events := processHcsResult(resultp) + if err != nil { + return nil, makeSystemError(computeSystem, operation, "", err, events) + } + + computeSystem.handle = handle + + if err = computeSystem.registerCallback(); err != nil { + return nil, makeSystemError(computeSystem, operation, "", err, nil) + } + + return computeSystem, nil +} + +// GetComputeSystems gets a list of the compute systems on the system that match the query +func GetComputeSystems(q schema1.ComputeSystemQuery) (_ []schema1.ContainerProperties, err error) { + operation := "hcsshim::GetComputeSystems" + fields := logrus.Fields{ + logfields.HCSOperation: operation, + } + logOperationBegin( + fields, + "hcsshim::ComputeSystem - Begin Operation") + + defer func() { + var result string + if err == nil { + result = "Success" + } else { + result = "Error" + } + + logOperationEnd( + fields, + "hcsshim::ComputeSystem - End Operation - "+result, + err) + }() + + queryb, err := json.Marshal(q) + if err != nil { + return nil, err + } + + query := string(queryb) + + logrus.WithFields(fields). + WithField(logfields.JSON, query). + Debug("HCS ComputeSystem Query") + + var ( + resultp *uint16 + computeSystemsp *uint16 + ) + completed := false + go syscallWatcher(fields, &completed) + err = hcsEnumerateComputeSystems(query, &computeSystemsp, &resultp) + completed = true + events := processHcsResult(resultp) + if err != nil { + return nil, &HcsError{Op: operation, Err: err, Events: events} + } + + if computeSystemsp == nil { + return nil, ErrUnexpectedValue + } + computeSystemsRaw := interop.ConvertAndFreeCoTaskMemBytes(computeSystemsp) + computeSystems := []schema1.ContainerProperties{} + if err = json.Unmarshal(computeSystemsRaw, &computeSystems); err != nil { + return nil, err + } + + return computeSystems, nil +} + +// Start synchronously starts the computeSystem. +func (computeSystem *System) Start() (err error) { + computeSystem.handleLock.RLock() + defer computeSystem.handleLock.RUnlock() + + operation := "hcsshim::ComputeSystem::Start" + computeSystem.logOperationBegin(operation) + defer computeSystem.logOperationEnd(err) + + if computeSystem.handle == 0 { + return makeSystemError(computeSystem, "Start", "", ErrAlreadyClosed, nil) + } + + // This is a very simple backoff-retry loop to limit the number + // of parallel container starts if environment variable + // HCSSHIM_MAX_PARALLEL_START is set to a positive integer. + // It should generally only be used as a workaround to various + // platform issues that exist between RS1 and RS4 as of Aug 2018 + if currentContainerStarts.maxParallel > 0 { + for { + currentContainerStarts.Lock() + if currentContainerStarts.inProgress < currentContainerStarts.maxParallel { + currentContainerStarts.inProgress++ + currentContainerStarts.Unlock() + break + } + if currentContainerStarts.inProgress == currentContainerStarts.maxParallel { + currentContainerStarts.Unlock() + time.Sleep(100 * time.Millisecond) + } + } + // Make sure we decrement the count when we are done. + defer func() { + currentContainerStarts.Lock() + currentContainerStarts.inProgress-- + currentContainerStarts.Unlock() + }() + } + + var resultp *uint16 + completed := false + go syscallWatcher(computeSystem.logctx, &completed) + err = hcsStartComputeSystem(computeSystem.handle, "", &resultp) + completed = true + events, err := processAsyncHcsResult(err, resultp, computeSystem.callbackNumber, hcsNotificationSystemStartCompleted, &timeout.SystemStart) + if err != nil { + return makeSystemError(computeSystem, "Start", "", err, events) + } + + return nil +} + +// ID returns the compute system's identifier. +func (computeSystem *System) ID() string { + return computeSystem.id +} + +// Shutdown requests a compute system shutdown, if IsPending() on the error returned is true, +// it may not actually be shut down until Wait() succeeds. +func (computeSystem *System) Shutdown() (err error) { + computeSystem.handleLock.RLock() + defer computeSystem.handleLock.RUnlock() + + operation := "hcsshim::ComputeSystem::Shutdown" + computeSystem.logOperationBegin(operation) + defer computeSystem.logOperationEnd(err) + + if computeSystem.handle == 0 { + return makeSystemError(computeSystem, "Shutdown", "", ErrAlreadyClosed, nil) + } + + var resultp *uint16 + completed := false + go syscallWatcher(computeSystem.logctx, &completed) + err = hcsShutdownComputeSystem(computeSystem.handle, "", &resultp) + completed = true + events := processHcsResult(resultp) + if err != nil { + return makeSystemError(computeSystem, "Shutdown", "", err, events) + } + + return nil +} + +// Terminate requests a compute system terminate, if IsPending() on the error returned is true, +// it may not actually be shut down until Wait() succeeds. +func (computeSystem *System) Terminate() (err error) { + computeSystem.handleLock.RLock() + defer computeSystem.handleLock.RUnlock() + + operation := "hcsshim::ComputeSystem::Terminate" + computeSystem.logOperationBegin(operation) + defer computeSystem.logOperationEnd(err) + + if computeSystem.handle == 0 { + return makeSystemError(computeSystem, "Terminate", "", ErrAlreadyClosed, nil) + } + + var resultp *uint16 + completed := false + go syscallWatcher(computeSystem.logctx, &completed) + err = hcsTerminateComputeSystem(computeSystem.handle, "", &resultp) + completed = true + events := processHcsResult(resultp) + if err != nil { + return makeSystemError(computeSystem, "Terminate", "", err, events) + } + + return nil +} + +// Wait synchronously waits for the compute system to shutdown or terminate. +func (computeSystem *System) Wait() (err error) { + operation := "hcsshim::ComputeSystem::Wait" + computeSystem.logOperationBegin(operation) + defer computeSystem.logOperationEnd(err) + + err = waitForNotification(computeSystem.callbackNumber, hcsNotificationSystemExited, nil) + if err != nil { + return makeSystemError(computeSystem, "Wait", "", err, nil) + } + + return nil +} + +// WaitTimeout synchronously waits for the compute system to terminate or the duration to elapse. +// If the timeout expires, IsTimeout(err) == true +func (computeSystem *System) WaitTimeout(timeout time.Duration) (err error) { + operation := "hcsshim::ComputeSystem::WaitTimeout" + computeSystem.logOperationBegin(operation) + defer computeSystem.logOperationEnd(err) + + err = waitForNotification(computeSystem.callbackNumber, hcsNotificationSystemExited, &timeout) + if err != nil { + return makeSystemError(computeSystem, "WaitTimeout", "", err, nil) + } + + return nil +} + +func (computeSystem *System) Properties(types ...schema1.PropertyType) (_ *schema1.ContainerProperties, err error) { + computeSystem.handleLock.RLock() + defer computeSystem.handleLock.RUnlock() + + operation := "hcsshim::ComputeSystem::Properties" + computeSystem.logOperationBegin(operation) + defer computeSystem.logOperationEnd(err) + + queryj, err := json.Marshal(schema1.PropertyQuery{types}) + if err != nil { + return nil, makeSystemError(computeSystem, "Properties", "", err, nil) + } + + logrus.WithFields(computeSystem.logctx). + WithField(logfields.JSON, queryj). + Debug("HCS ComputeSystem Properties Query") + + var resultp, propertiesp *uint16 + completed := false + go syscallWatcher(computeSystem.logctx, &completed) + err = hcsGetComputeSystemProperties(computeSystem.handle, string(queryj), &propertiesp, &resultp) + completed = true + events := processHcsResult(resultp) + if err != nil { + return nil, makeSystemError(computeSystem, "Properties", "", err, events) + } + + if propertiesp == nil { + return nil, ErrUnexpectedValue + } + propertiesRaw := interop.ConvertAndFreeCoTaskMemBytes(propertiesp) + properties := &schema1.ContainerProperties{} + if err := json.Unmarshal(propertiesRaw, properties); err != nil { + return nil, makeSystemError(computeSystem, "Properties", "", err, nil) + } + + return properties, nil +} + +// Pause pauses the execution of the computeSystem. This feature is not enabled in TP5. +func (computeSystem *System) Pause() (err error) { + computeSystem.handleLock.RLock() + defer computeSystem.handleLock.RUnlock() + + operation := "hcsshim::ComputeSystem::Pause" + computeSystem.logOperationBegin(operation) + defer computeSystem.logOperationEnd(err) + + if computeSystem.handle == 0 { + return makeSystemError(computeSystem, "Pause", "", ErrAlreadyClosed, nil) + } + + var resultp *uint16 + completed := false + go syscallWatcher(computeSystem.logctx, &completed) + err = hcsPauseComputeSystem(computeSystem.handle, "", &resultp) + completed = true + events, err := processAsyncHcsResult(err, resultp, computeSystem.callbackNumber, hcsNotificationSystemPauseCompleted, &timeout.SystemPause) + if err != nil { + return makeSystemError(computeSystem, "Pause", "", err, events) + } + + return nil +} + +// Resume resumes the execution of the computeSystem. This feature is not enabled in TP5. +func (computeSystem *System) Resume() (err error) { + computeSystem.handleLock.RLock() + defer computeSystem.handleLock.RUnlock() + + operation := "hcsshim::ComputeSystem::Resume" + computeSystem.logOperationBegin(operation) + defer computeSystem.logOperationEnd(err) + + if computeSystem.handle == 0 { + return makeSystemError(computeSystem, "Resume", "", ErrAlreadyClosed, nil) + } + + var resultp *uint16 + completed := false + go syscallWatcher(computeSystem.logctx, &completed) + err = hcsResumeComputeSystem(computeSystem.handle, "", &resultp) + completed = true + events, err := processAsyncHcsResult(err, resultp, computeSystem.callbackNumber, hcsNotificationSystemResumeCompleted, &timeout.SystemResume) + if err != nil { + return makeSystemError(computeSystem, "Resume", "", err, events) + } + + return nil +} + +// CreateProcess launches a new process within the computeSystem. +func (computeSystem *System) CreateProcess(c interface{}) (_ *Process, err error) { + computeSystem.handleLock.RLock() + defer computeSystem.handleLock.RUnlock() + + operation := "hcsshim::ComputeSystem::CreateProcess" + computeSystem.logOperationBegin(operation) + defer computeSystem.logOperationEnd(err) + + var ( + processInfo hcsProcessInformation + processHandle hcsProcess + resultp *uint16 + ) + + if computeSystem.handle == 0 { + return nil, makeSystemError(computeSystem, "CreateProcess", "", ErrAlreadyClosed, nil) + } + + configurationb, err := json.Marshal(c) + if err != nil { + return nil, makeSystemError(computeSystem, "CreateProcess", "", err, nil) + } + + configuration := string(configurationb) + + logrus.WithFields(computeSystem.logctx). + WithField(logfields.JSON, configuration). + Debug("HCS ComputeSystem Process Document") + + completed := false + go syscallWatcher(computeSystem.logctx, &completed) + err = hcsCreateProcess(computeSystem.handle, configuration, &processInfo, &processHandle, &resultp) + completed = true + events := processHcsResult(resultp) + if err != nil { + return nil, makeSystemError(computeSystem, "CreateProcess", configuration, err, events) + } + + logrus.WithFields(computeSystem.logctx). + WithField(logfields.ProcessID, processInfo.ProcessId). + Debug("HCS ComputeSystem CreateProcess PID") + + process := newProcess(processHandle, int(processInfo.ProcessId), computeSystem) + process.cachedPipes = &cachedPipes{ + stdIn: processInfo.StdInput, + stdOut: processInfo.StdOutput, + stdErr: processInfo.StdError, + } + + if err = process.registerCallback(); err != nil { + return nil, makeSystemError(computeSystem, "CreateProcess", "", err, nil) + } + + return process, nil +} + +// OpenProcess gets an interface to an existing process within the computeSystem. +func (computeSystem *System) OpenProcess(pid int) (_ *Process, err error) { + computeSystem.handleLock.RLock() + defer computeSystem.handleLock.RUnlock() + + // Add PID for the context of this operation + computeSystem.logctx[logfields.ProcessID] = pid + defer delete(computeSystem.logctx, logfields.ProcessID) + + operation := "hcsshim::ComputeSystem::OpenProcess" + computeSystem.logOperationBegin(operation) + defer computeSystem.logOperationEnd(err) + + var ( + processHandle hcsProcess + resultp *uint16 + ) + + if computeSystem.handle == 0 { + return nil, makeSystemError(computeSystem, "OpenProcess", "", ErrAlreadyClosed, nil) + } + + completed := false + go syscallWatcher(computeSystem.logctx, &completed) + err = hcsOpenProcess(computeSystem.handle, uint32(pid), &processHandle, &resultp) + completed = true + events := processHcsResult(resultp) + if err != nil { + return nil, makeSystemError(computeSystem, "OpenProcess", "", err, events) + } + + process := newProcess(processHandle, pid, computeSystem) + if err = process.registerCallback(); err != nil { + return nil, makeSystemError(computeSystem, "OpenProcess", "", err, nil) + } + + return process, nil +} + +// Close cleans up any state associated with the compute system but does not terminate or wait for it. +func (computeSystem *System) Close() (err error) { + computeSystem.handleLock.Lock() + defer computeSystem.handleLock.Unlock() + + operation := "hcsshim::ComputeSystem::Close" + computeSystem.logOperationBegin(operation) + defer computeSystem.logOperationEnd(err) + + // Don't double free this + if computeSystem.handle == 0 { + return nil + } + + if err = computeSystem.unregisterCallback(); err != nil { + return makeSystemError(computeSystem, "Close", "", err, nil) + } + + completed := false + go syscallWatcher(computeSystem.logctx, &completed) + err = hcsCloseComputeSystem(computeSystem.handle) + completed = true + if err != nil { + return makeSystemError(computeSystem, "Close", "", err, nil) + } + + computeSystem.handle = 0 + + return nil +} + +func (computeSystem *System) registerCallback() error { + context := ¬ifcationWatcherContext{ + channels: newChannels(), + } + + callbackMapLock.Lock() + callbackNumber := nextCallback + nextCallback++ + callbackMap[callbackNumber] = context + callbackMapLock.Unlock() + + var callbackHandle hcsCallback + err := hcsRegisterComputeSystemCallback(computeSystem.handle, notificationWatcherCallback, callbackNumber, &callbackHandle) + if err != nil { + return err + } + context.handle = callbackHandle + computeSystem.callbackNumber = callbackNumber + + return nil +} + +func (computeSystem *System) unregisterCallback() error { + callbackNumber := computeSystem.callbackNumber + + callbackMapLock.RLock() + context := callbackMap[callbackNumber] + callbackMapLock.RUnlock() + + if context == nil { + return nil + } + + handle := context.handle + + if handle == 0 { + return nil + } + + // hcsUnregisterComputeSystemCallback has its own syncronization + // to wait for all callbacks to complete. We must NOT hold the callbackMapLock. + err := hcsUnregisterComputeSystemCallback(handle) + if err != nil { + return err + } + + closeChannels(context.channels) + + callbackMapLock.Lock() + callbackMap[callbackNumber] = nil + callbackMapLock.Unlock() + + handle = 0 + + return nil +} + +// Modify the System by sending a request to HCS +func (computeSystem *System) Modify(config interface{}) (err error) { + computeSystem.handleLock.RLock() + defer computeSystem.handleLock.RUnlock() + + operation := "hcsshim::ComputeSystem::Modify" + computeSystem.logOperationBegin(operation) + defer computeSystem.logOperationEnd(err) + + if computeSystem.handle == 0 { + return makeSystemError(computeSystem, "Modify", "", ErrAlreadyClosed, nil) + } + + requestJSON, err := json.Marshal(config) + if err != nil { + return err + } + + requestString := string(requestJSON) + + logrus.WithFields(computeSystem.logctx). + WithField(logfields.JSON, requestString). + Debug("HCS ComputeSystem Modify Document") + + var resultp *uint16 + completed := false + go syscallWatcher(computeSystem.logctx, &completed) + err = hcsModifyComputeSystem(computeSystem.handle, requestString, &resultp) + completed = true + events := processHcsResult(resultp) + if err != nil { + return makeSystemError(computeSystem, "Modify", requestString, err, events) + } + + return nil +} |