123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720 |
- 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
- closedWaitOnce sync.Once
- waitBlock chan struct{}
- waitError error
- }
- func newSystem(id string) *System {
- return &System{
- id: id,
- logctx: logrus.Fields{
- logfields.ContainerID: id,
- },
- waitBlock: make(chan struct{}),
- }
- }
- func (computeSystem *System) logOperationBegin(operation string) {
- logOperationBegin(
- computeSystem.logctx,
- operation+" - Begin Operation")
- }
- func (computeSystem *System) logOperationEnd(operation string, err error) {
- var result string
- if err == nil {
- result = "Success"
- } else {
- result = "Error"
- }
- logOperationEnd(
- computeSystem.logctx,
- operation+" - End Operation - "+result,
- err)
- }
- // 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 func() { computeSystem.logOperationEnd(operation, 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
- createError error
- )
- syscallWatcher(computeSystem.logctx, func() {
- createError = hcsCreateComputeSystem(id, hcsDocument, identity, &computeSystem.handle, &resultp)
- })
- 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)
- }
- go computeSystem.waitBackground()
- 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 func() {
- if IsNotExist(err) {
- computeSystem.logOperationEnd(operation, nil)
- } else {
- computeSystem.logOperationEnd(operation, 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)
- }
- go computeSystem.waitBackground()
- 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{}
- logOperationBegin(
- fields,
- operation+" - Begin Operation")
- defer func() {
- var result string
- if err == nil {
- result = "Success"
- } else {
- result = "Error"
- }
- logOperationEnd(
- fields,
- operation+" - 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
- )
- syscallWatcher(fields, func() {
- err = hcsEnumerateComputeSystems(query, &computeSystemsp, &resultp)
- })
- 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 func() { computeSystem.logOperationEnd(operation, 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
- syscallWatcher(computeSystem.logctx, func() {
- err = hcsStartComputeSystem(computeSystem.handle, "", &resultp)
- })
- 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 func() {
- if IsAlreadyClosed(err) || IsAlreadyStopped(err) || IsPending(err) {
- computeSystem.logOperationEnd(operation, nil)
- } else {
- computeSystem.logOperationEnd(operation, err)
- }
- }()
- if computeSystem.handle == 0 {
- return makeSystemError(computeSystem, "Shutdown", "", ErrAlreadyClosed, nil)
- }
- var resultp *uint16
- syscallWatcher(computeSystem.logctx, func() {
- err = hcsShutdownComputeSystem(computeSystem.handle, "", &resultp)
- })
- 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 func() {
- if IsAlreadyClosed(err) || IsAlreadyStopped(err) || IsPending(err) {
- computeSystem.logOperationEnd(operation, nil)
- } else {
- computeSystem.logOperationEnd(operation, err)
- }
- }()
- if computeSystem.handle == 0 {
- return makeSystemError(computeSystem, "Terminate", "", ErrAlreadyClosed, nil)
- }
- var resultp *uint16
- syscallWatcher(computeSystem.logctx, func() {
- err = hcsTerminateComputeSystem(computeSystem.handle, "", &resultp)
- })
- events := processHcsResult(resultp)
- if err != nil && err != ErrVmcomputeAlreadyStopped {
- return makeSystemError(computeSystem, "Terminate", "", err, events)
- }
- return nil
- }
- // waitBackground waits for the compute system exit notification. Once received
- // sets `computeSystem.waitError` (if any) and unblocks all `Wait`,
- // `WaitExpectedError`, and `WaitTimeout` calls.
- //
- // This MUST be called exactly once per `computeSystem.handle` but `Wait`,
- // `WaitExpectedError`, and `WaitTimeout` are safe to call multiple times.
- func (computeSystem *System) waitBackground() {
- computeSystem.waitError = waitForNotification(computeSystem.callbackNumber, hcsNotificationSystemExited, nil)
- computeSystem.closedWaitOnce.Do(func() {
- close(computeSystem.waitBlock)
- })
- }
- // Wait synchronously waits for the compute system to shutdown or terminate. If
- // the compute system has already exited returns the previous error (if any).
- func (computeSystem *System) Wait() (err error) {
- operation := "hcsshim::ComputeSystem::Wait"
- computeSystem.logOperationBegin(operation)
- defer func() { computeSystem.logOperationEnd(operation, err) }()
- <-computeSystem.waitBlock
- if computeSystem.waitError != nil {
- return makeSystemError(computeSystem, "Wait", "", computeSystem.waitError, nil)
- }
- return nil
- }
- // WaitExpectedError synchronously waits for the compute system to shutdown or
- // terminate and returns the error (if any) as long as it does not match
- // `expected`. If the compute system has already exited returns the previous
- // error (if any) as long as it does not match `expected`.
- func (computeSystem *System) WaitExpectedError(expected error) (err error) {
- operation := "hcsshim::ComputeSystem::WaitExpectedError"
- computeSystem.logOperationBegin(operation)
- defer func() { computeSystem.logOperationEnd(operation, err) }()
- <-computeSystem.waitBlock
- if computeSystem.waitError != nil && getInnerError(computeSystem.waitError) != expected {
- return makeSystemError(computeSystem, "WaitExpectedError", "", computeSystem.waitError, nil)
- }
- return nil
- }
- // WaitTimeout synchronously waits for the compute system to terminate or the
- // duration to elapse. If the timeout expires, `IsTimeout(err) == true`. If
- // the compute system has already exited returns the previous error (if any).
- func (computeSystem *System) WaitTimeout(timeout time.Duration) (err error) {
- operation := "hcsshim::ComputeSystem::WaitTimeout"
- computeSystem.logOperationBegin(operation)
- defer func() { computeSystem.logOperationEnd(operation, err) }()
- select {
- case <-computeSystem.waitBlock:
- if computeSystem.waitError != nil {
- return makeSystemError(computeSystem, "WaitTimeout", "", computeSystem.waitError, nil)
- }
- return nil
- case <-time.After(timeout):
- return makeSystemError(computeSystem, "WaitTimeout", "", ErrTimeout, 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 func() { computeSystem.logOperationEnd(operation, err) }()
- queryBytes, err := json.Marshal(schema1.PropertyQuery{PropertyTypes: types})
- if err != nil {
- return nil, makeSystemError(computeSystem, "Properties", "", err, nil)
- }
- queryString := string(queryBytes)
- logrus.WithFields(computeSystem.logctx).
- WithField(logfields.JSON, queryString).
- Debug("HCS ComputeSystem Properties Query")
- var resultp, propertiesp *uint16
- syscallWatcher(computeSystem.logctx, func() {
- err = hcsGetComputeSystemProperties(computeSystem.handle, string(queryString), &propertiesp, &resultp)
- })
- 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 func() { computeSystem.logOperationEnd(operation, err) }()
- if computeSystem.handle == 0 {
- return makeSystemError(computeSystem, "Pause", "", ErrAlreadyClosed, nil)
- }
- var resultp *uint16
- syscallWatcher(computeSystem.logctx, func() {
- err = hcsPauseComputeSystem(computeSystem.handle, "", &resultp)
- })
- 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 func() { computeSystem.logOperationEnd(operation, err) }()
- if computeSystem.handle == 0 {
- return makeSystemError(computeSystem, "Resume", "", ErrAlreadyClosed, nil)
- }
- var resultp *uint16
- syscallWatcher(computeSystem.logctx, func() {
- err = hcsResumeComputeSystem(computeSystem.handle, "", &resultp)
- })
- 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 func() { computeSystem.logOperationEnd(operation, 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")
- syscallWatcher(computeSystem.logctx, func() {
- err = hcsCreateProcess(computeSystem.handle, configuration, &processInfo, &processHandle, &resultp)
- })
- 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)
- }
- go process.waitBackground()
- 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 func() { computeSystem.logOperationEnd(operation, err) }()
- var (
- processHandle hcsProcess
- resultp *uint16
- )
- if computeSystem.handle == 0 {
- return nil, makeSystemError(computeSystem, "OpenProcess", "", ErrAlreadyClosed, nil)
- }
- syscallWatcher(computeSystem.logctx, func() {
- err = hcsOpenProcess(computeSystem.handle, uint32(pid), &processHandle, &resultp)
- })
- 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)
- }
- go process.waitBackground()
- 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 func() { computeSystem.logOperationEnd(operation, err) }()
- // Don't double free this
- if computeSystem.handle == 0 {
- return nil
- }
- if err = computeSystem.unregisterCallback(); err != nil {
- return makeSystemError(computeSystem, "Close", "", err, nil)
- }
- syscallWatcher(computeSystem.logctx, func() {
- err = hcsCloseComputeSystem(computeSystem.handle)
- })
- if err != nil {
- return makeSystemError(computeSystem, "Close", "", err, nil)
- }
- computeSystem.handle = 0
- computeSystem.closedWaitOnce.Do(func() {
- close(computeSystem.waitBlock)
- })
- return nil
- }
- func (computeSystem *System) registerCallback() error {
- context := ¬ifcationWatcherContext{
- channels: newSystemChannels(),
- systemID: computeSystem.id,
- }
- 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()
- delete(callbackMap, callbackNumber)
- 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 func() { computeSystem.logOperationEnd(operation, 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
- syscallWatcher(computeSystem.logctx, func() {
- err = hcsModifyComputeSystem(computeSystem.handle, requestString, &resultp)
- })
- events := processHcsResult(resultp)
- if err != nil {
- return makeSystemError(computeSystem, "Modify", requestString, err, events)
- }
- return nil
- }
|