mirror of https://github.com/docker/cli.git
337 lines
10 KiB
Go
337 lines
10 KiB
Go
package hcs
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"syscall"
|
|
|
|
"github.com/Microsoft/hcsshim/internal/log"
|
|
)
|
|
|
|
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)
|
|
|
|
// ErrVmcomputeUnexpectedExit is an error encountered when the compute system terminates unexpectedly
|
|
ErrVmcomputeUnexpectedExit = syscall.Errno(0xC0370106)
|
|
|
|
// 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
|
|
}
|
|
|
|
func processHcsResult(ctx context.Context, resultJSON string) []ErrorEvent {
|
|
if resultJSON != "" {
|
|
result := &hcsResult{}
|
|
if err := json.Unmarshal([]byte(resultJSON), result); err != nil {
|
|
log.G(ctx).WithError(err).Warning("Could not unmarshal HCS result")
|
|
return nil
|
|
}
|
|
return result.ErrorEvents
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type HcsError struct {
|
|
Op string
|
|
Err error
|
|
Events []ErrorEvent
|
|
}
|
|
|
|
var _ net.Error = &HcsError{}
|
|
|
|
func (e *HcsError) Error() string {
|
|
s := e.Op + ": " + e.Err.Error()
|
|
for _, ev := range e.Events {
|
|
s += "\n" + ev.String()
|
|
}
|
|
return s
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
var _ net.Error = &ProcessError{}
|
|
|
|
// 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
|
|
}
|
|
|
|
var _ net.Error = &SystemError{}
|
|
|
|
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
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
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 {
|
|
if err, ok := err.(net.Error); ok && err.Timeout() {
|
|
return true
|
|
}
|
|
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
|
|
}
|
|
|
|
// IsOperationInvalidState returns true when err is caused by
|
|
// `ErrVmcomputeOperationInvalidState`.
|
|
func IsOperationInvalidState(err error) bool {
|
|
err = getInnerError(err)
|
|
return err == ErrVmcomputeOperationInvalidState
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func getOperationLogResult(err error) (string, error) {
|
|
switch err {
|
|
case nil:
|
|
return "Success", nil
|
|
default:
|
|
return "Error", err
|
|
}
|
|
}
|