2018-09-05 14:54:38 -04:00
package hcs
import (
2020-01-06 12:15:00 -05:00
"context"
2018-09-05 14:54:38 -04:00
"encoding/json"
"errors"
"fmt"
2020-01-06 12:15:00 -05:00
"net"
2018-09-05 14:54:38 -04:00
"syscall"
2020-01-06 12:15:00 -05:00
"github.com/Microsoft/hcsshim/internal/log"
2018-09-05 14:54:38 -04:00
)
var (
// ErrComputeSystemDoesNotExist is an error encountered when the container being operated on no longer exists
ErrComputeSystemDoesNotExist = syscall . Errno ( 0xc037010e )
// ErrElementNotFound is an error encountered when the object being referenced does not exist
ErrElementNotFound = syscall . Errno ( 0x490 )
// ErrElementNotFound is an error encountered when the object being referenced does not exist
ErrNotSupported = syscall . Errno ( 0x32 )
// ErrInvalidData is an error encountered when the request being sent to hcs is invalid/unsupported
// decimal -2147024883 / hex 0x8007000d
ErrInvalidData = syscall . Errno ( 0xd )
// ErrHandleClose is an error encountered when the handle generating the notification being waited on has been closed
ErrHandleClose = errors . New ( "hcsshim: the handle generating this notification has been closed" )
// ErrAlreadyClosed is an error encountered when using a handle that has been closed by the Close method
ErrAlreadyClosed = errors . New ( "hcsshim: the handle has already been closed" )
// ErrInvalidNotificationType is an error encountered when an invalid notification type is used
ErrInvalidNotificationType = errors . New ( "hcsshim: invalid notification type" )
// ErrInvalidProcessState is an error encountered when the process is not in a valid state for the requested operation
ErrInvalidProcessState = errors . New ( "the process is in an invalid state for the attempted operation" )
// ErrTimeout is an error encountered when waiting on a notification times out
ErrTimeout = errors . New ( "hcsshim: timeout waiting for notification" )
// ErrUnexpectedContainerExit is the error encountered when a container exits while waiting for
// a different expected notification
ErrUnexpectedContainerExit = errors . New ( "unexpected container exit" )
// ErrUnexpectedProcessAbort is the error encountered when communication with the compute service
// is lost while waiting for a notification
ErrUnexpectedProcessAbort = errors . New ( "lost communication with compute service" )
// ErrUnexpectedValue is an error encountered when hcs returns an invalid value
ErrUnexpectedValue = errors . New ( "unexpected value returned from hcs" )
// ErrVmcomputeAlreadyStopped is an error encountered when a shutdown or terminate request is made on a stopped container
ErrVmcomputeAlreadyStopped = syscall . Errno ( 0xc0370110 )
// ErrVmcomputeOperationPending is an error encountered when the operation is being completed asynchronously
ErrVmcomputeOperationPending = syscall . Errno ( 0xC0370103 )
// ErrVmcomputeOperationInvalidState is an error encountered when the compute system is not in a valid state for the requested operation
ErrVmcomputeOperationInvalidState = syscall . Errno ( 0xc0370105 )
// ErrProcNotFound is an error encountered when the the process cannot be found
ErrProcNotFound = syscall . Errno ( 0x7f )
// ErrVmcomputeOperationAccessIsDenied is an error which can be encountered when enumerating compute systems in RS1/RS2
// builds when the underlying silo might be in the process of terminating. HCS was fixed in RS3.
ErrVmcomputeOperationAccessIsDenied = syscall . Errno ( 0x5 )
// ErrVmcomputeInvalidJSON is an error encountered when the compute system does not support/understand the messages sent by management
ErrVmcomputeInvalidJSON = syscall . Errno ( 0xc037010d )
// ErrVmcomputeUnknownMessage is an error encountered guest compute system doesn't support the message
ErrVmcomputeUnknownMessage = syscall . Errno ( 0xc037010b )
2019-02-28 13:16:30 -05:00
// ErrVmcomputeUnexpectedExit is an error encountered when the compute system terminates unexpectedly
ErrVmcomputeUnexpectedExit = syscall . Errno ( 0xC0370106 )
2018-09-05 14:54:38 -04:00
// ErrNotSupported is an error encountered when hcs doesn't support the request
ErrPlatformNotSupported = errors . New ( "unsupported platform request" )
)
type ErrorEvent struct {
Message string ` json:"Message,omitempty" ` // Fully formated error message
StackTrace string ` json:"StackTrace,omitempty" ` // Stack trace in string form
Provider string ` json:"Provider,omitempty" `
EventID uint16 ` json:"EventId,omitempty" `
Flags uint32 ` json:"Flags,omitempty" `
Source string ` json:"Source,omitempty" `
//Data []EventData `json:"Data,omitempty"` // Omit this as HCS doesn't encode this well. It's more confusing to include. It is however logged in debug mode (see processHcsResult function)
}
type hcsResult struct {
Error int32
ErrorMessage string
ErrorEvents [ ] ErrorEvent ` json:"ErrorEvents,omitempty" `
}
func ( ev * ErrorEvent ) String ( ) string {
evs := "[Event Detail: " + ev . Message
if ev . StackTrace != "" {
evs += " Stack Trace: " + ev . StackTrace
}
if ev . Provider != "" {
evs += " Provider: " + ev . Provider
}
if ev . EventID != 0 {
evs = fmt . Sprintf ( "%s EventID: %d" , evs , ev . EventID )
}
if ev . Flags != 0 {
evs = fmt . Sprintf ( "%s flags: %d" , evs , ev . Flags )
}
if ev . Source != "" {
evs += " Source: " + ev . Source
}
evs += "]"
return evs
}
2020-01-06 12:15:00 -05:00
func processHcsResult ( ctx context . Context , resultJSON string ) [ ] ErrorEvent {
if resultJSON != "" {
2018-09-05 14:54:38 -04:00
result := & hcsResult { }
2020-01-06 12:15:00 -05:00
if err := json . Unmarshal ( [ ] byte ( resultJSON ) , result ) ; err != nil {
log . G ( ctx ) . WithError ( err ) . Warning ( "Could not unmarshal HCS result" )
2018-09-05 14:54:38 -04:00
return nil
}
return result . ErrorEvents
}
return nil
}
type HcsError struct {
Op string
Err error
Events [ ] ErrorEvent
}
2020-01-06 12:15:00 -05:00
var _ net . Error = & HcsError { }
2018-09-05 14:54:38 -04:00
func ( e * HcsError ) Error ( ) string {
s := e . Op + ": " + e . Err . Error ( )
for _ , ev := range e . Events {
s += "\n" + ev . String ( )
}
return s
}
2020-01-06 12:15:00 -05:00
func ( e * HcsError ) Temporary ( ) bool {
err , ok := e . Err . ( net . Error )
return ok && err . Temporary ( )
}
func ( e * HcsError ) Timeout ( ) bool {
err , ok := e . Err . ( net . Error )
return ok && err . Timeout ( )
}
2018-09-05 14:54:38 -04:00
// ProcessError is an error encountered in HCS during an operation on a Process object
type ProcessError struct {
SystemID string
Pid int
Op string
Err error
Events [ ] ErrorEvent
}
2020-01-06 12:15:00 -05:00
var _ net . Error = & ProcessError { }
2018-09-05 14:54:38 -04:00
// SystemError is an error encountered in HCS during an operation on a Container object
type SystemError struct {
ID string
Op string
Err error
Extra string
Events [ ] ErrorEvent
}
2020-01-06 12:15:00 -05:00
var _ net . Error = & SystemError { }
2018-09-05 14:54:38 -04:00
func ( e * SystemError ) Error ( ) string {
s := e . Op + " " + e . ID + ": " + e . Err . Error ( )
for _ , ev := range e . Events {
s += "\n" + ev . String ( )
}
if e . Extra != "" {
s += "\n(extra info: " + e . Extra + ")"
}
return s
}
2020-01-06 12:15:00 -05:00
func ( e * SystemError ) Temporary ( ) bool {
err , ok := e . Err . ( net . Error )
return ok && err . Temporary ( )
}
func ( e * SystemError ) Timeout ( ) bool {
err , ok := e . Err . ( net . Error )
return ok && err . Timeout ( )
}
2018-09-05 14:54:38 -04:00
func makeSystemError ( system * System , op string , extra string , err error , events [ ] ErrorEvent ) error {
// Don't double wrap errors
if _ , ok := err . ( * SystemError ) ; ok {
return err
}
return & SystemError {
ID : system . ID ( ) ,
Op : op ,
Extra : extra ,
Err : err ,
Events : events ,
}
}
func ( e * ProcessError ) Error ( ) string {
s := fmt . Sprintf ( "%s %s:%d: %s" , e . Op , e . SystemID , e . Pid , e . Err . Error ( ) )
for _ , ev := range e . Events {
s += "\n" + ev . String ( )
}
return s
}
2020-01-06 12:15:00 -05:00
func ( e * ProcessError ) Temporary ( ) bool {
err , ok := e . Err . ( net . Error )
return ok && err . Temporary ( )
}
func ( e * ProcessError ) Timeout ( ) bool {
err , ok := e . Err . ( net . Error )
return ok && err . Timeout ( )
}
2018-09-05 14:54:38 -04:00
func makeProcessError ( process * Process , op string , err error , events [ ] ErrorEvent ) error {
// Don't double wrap errors
if _ , ok := err . ( * ProcessError ) ; ok {
return err
}
return & ProcessError {
Pid : process . Pid ( ) ,
SystemID : process . SystemID ( ) ,
Op : op ,
Err : err ,
Events : events ,
}
}
// IsNotExist checks if an error is caused by the Container or Process not existing.
// Note: Currently, ErrElementNotFound can mean that a Process has either
// already exited, or does not exist. Both IsAlreadyStopped and IsNotExist
// will currently return true when the error is ErrElementNotFound or ErrProcNotFound.
func IsNotExist ( err error ) bool {
err = getInnerError ( err )
return err == ErrComputeSystemDoesNotExist ||
err == ErrElementNotFound ||
err == ErrProcNotFound
}
// IsAlreadyClosed checks if an error is caused by the Container or Process having been
// already closed by a call to the Close() method.
func IsAlreadyClosed ( err error ) bool {
err = getInnerError ( err )
return err == ErrAlreadyClosed
}
// IsPending returns a boolean indicating whether the error is that
// the requested operation is being completed in the background.
func IsPending ( err error ) bool {
err = getInnerError ( err )
return err == ErrVmcomputeOperationPending
}
// IsTimeout returns a boolean indicating whether the error is caused by
// a timeout waiting for the operation to complete.
func IsTimeout ( err error ) bool {
2020-01-06 12:15:00 -05:00
if err , ok := err . ( net . Error ) ; ok && err . Timeout ( ) {
return true
}
2018-09-05 14:54:38 -04:00
err = getInnerError ( err )
return err == ErrTimeout
}
// IsAlreadyStopped returns a boolean indicating whether the error is caused by
// a Container or Process being already stopped.
// Note: Currently, ErrElementNotFound can mean that a Process has either
// already exited, or does not exist. Both IsAlreadyStopped and IsNotExist
// will currently return true when the error is ErrElementNotFound or ErrProcNotFound.
func IsAlreadyStopped ( err error ) bool {
err = getInnerError ( err )
return err == ErrVmcomputeAlreadyStopped ||
err == ErrElementNotFound ||
err == ErrProcNotFound
}
// IsNotSupported returns a boolean indicating whether the error is caused by
// unsupported platform requests
// Note: Currently Unsupported platform requests can be mean either
// ErrVmcomputeInvalidJSON, ErrInvalidData, ErrNotSupported or ErrVmcomputeUnknownMessage
// is thrown from the Platform
func IsNotSupported ( err error ) bool {
err = getInnerError ( err )
// If Platform doesn't recognize or support the request sent, below errors are seen
return err == ErrVmcomputeInvalidJSON ||
err == ErrInvalidData ||
err == ErrNotSupported ||
err == ErrVmcomputeUnknownMessage
}
2019-04-24 14:14:51 -04:00
// IsOperationInvalidState returns true when err is caused by
// `ErrVmcomputeOperationInvalidState`.
func IsOperationInvalidState ( err error ) bool {
err = getInnerError ( err )
return err == ErrVmcomputeOperationInvalidState
}
2018-09-05 14:54:38 -04:00
func getInnerError ( err error ) error {
switch pe := err . ( type ) {
case nil :
return nil
case * HcsError :
err = pe . Err
case * SystemError :
err = pe . Err
case * ProcessError :
err = pe . Err
}
return err
}
2020-01-06 12:15:00 -05:00
func getOperationLogResult ( err error ) ( string , error ) {
switch err {
case nil :
return "Success" , nil
default :
return "Error" , err
}
}