diff --git a/cmd/containerd-shim-runhcs-v1/task_hcs.go b/cmd/containerd-shim-runhcs-v1/task_hcs.go index 6815632c9f..3216dc9689 100644 --- a/cmd/containerd-shim-runhcs-v1/task_hcs.go +++ b/cmd/containerd-shim-runhcs-v1/task_hcs.go @@ -520,7 +520,7 @@ func (ht *hcsTask) close(ctx context.Context) { werr = ht.c.Wait() close(ch) }() - err := ht.c.Shutdown(ctx) + err := ht.c.Shutdown(ctx, "") if err != nil { log.G(ctx).WithError(err).Error("failed to shutdown container") } else { diff --git a/cmd/runhcs/shim.go b/cmd/runhcs/shim.go index 0d052611f5..9bb5561e99 100644 --- a/cmd/runhcs/shim.go +++ b/cmd/runhcs/shim.go @@ -214,7 +214,7 @@ var shimCommand = cli.Command{ // Shutdown the container, waiting 5 minutes before terminating is // forcefully. const shutdownTimeout = time.Minute * 5 - err := c.hc.Shutdown(gcontext.Background()) + err := c.hc.Shutdown(gcontext.Background(), "") if err != nil { select { case <-containerExitCh: diff --git a/container.go b/container.go index 53c0a3854a..bc60e0a857 100644 --- a/container.go +++ b/container.go @@ -104,7 +104,7 @@ func (container *container) Start() error { // Shutdown requests a container shutdown, but it may not actually be shutdown until Wait() succeeds. func (container *container) Shutdown() error { - err := container.system.Shutdown(context.Background()) + err := container.system.Shutdown(context.Background(), "") if err != nil { return convertSystemError(err, container) } diff --git a/hcn/hcn.go b/hcn/hcn.go index da741449b8..7da8c860ce 100644 --- a/hcn/hcn.go +++ b/hcn/hcn.go @@ -27,6 +27,8 @@ import ( //sys hcnQueryNetworkProperties(network hcnNetwork, query string, properties **uint16, result **uint16) (hr error) = computenetwork.HcnQueryNetworkProperties? //sys hcnDeleteNetwork(id *_guid, result **uint16) (hr error) = computenetwork.HcnDeleteNetwork? //sys hcnCloseNetwork(network hcnNetwork) (hr error) = computenetwork.HcnCloseNetwork? +//sys hcsRegisterNetworkCallback(network hcnNetwork, callback uintptr, context uintptr, callbackHandle *hcnCallbackHandle) (hr error) = computenetwork.HcnRegisterNetworkCallback? +//sys hcsUnregisterNetworkCallback(callbackHandle hcnCallbackHandle) (hr error) = computenetwork.HcnUnregisterNetworkCallback? // Endpoint //sys hcnEnumerateEndpoints(query string, endpoints **uint16, result **uint16) (hr error) = computenetwork.HcnEnumerateEndpoints? @@ -57,7 +59,7 @@ import ( // Service //sys hcnOpenService(service *hcnService, result **uint16) (hr error) = computenetwork.HcnOpenService? -//sys hcnRegisterServiceCallback(service hcnService, callback int32, context int32, callbackHandle *hcnCallbackHandle) (hr error) = computenetwork.HcnRegisterServiceCallback? +//sys hcnRegisterServiceCallback(callback uintptr, context uintptr, callbackHandle *hcnCallbackHandle) (hr error) = computenetwork.HcnRegisterServiceCallback? //sys hcnUnregisterServiceCallback(callbackHandle hcnCallbackHandle) (hr error) = computenetwork.HcnUnregisterServiceCallback? //sys hcnCloseService(service hcnService) (hr error) = computenetwork.HcnCloseService? diff --git a/hcn/hcncallback.go b/hcn/hcncallback.go new file mode 100644 index 0000000000..d7f1fd193e --- /dev/null +++ b/hcn/hcncallback.go @@ -0,0 +1,125 @@ +package hcn + +import ( + "encoding/json" + "fmt" + "sync" + "syscall" + "unsafe" + + "github.com/Microsoft/hcsshim/internal/interop" +) + +var ( + nextCallback uintptr + callbackMap = map[uintptr]*notifcationWatcherContext{} + callbackMapLock = sync.RWMutex{} + + notificationWatcherCallback = syscall.NewCallback(notificationWatcher) + + // Notifications for HCN_SERVICE handles + HcnNotificationNetworkPreCreate HcnNotification = 0x00000001 + HcnNotificationNetworkCreate HcnNotification = 0x00000002 + HcnNotificationNetworkPreDelete HcnNotification = 0x00000003 + HcnNotificationNetworkDelete HcnNotification = 0x00000004 + + // Namespace Notifications + HcnNotificationNamespaceCreate HcnNotification = 0x00000005 + HcnNotificationNamespaceDelete HcnNotification = 0x00000006 + + // Notifications for HCN_SERVICE handles + HcnNotificationGuestNetworkServiceCreate HcnNotification = 0x00000007 + HcnNotificationGuestNetworkServiceDelete HcnNotification = 0x00000008 + + // Notifications for HCN_NETWORK handles + HcnNotificationNetworkEndpointAttached HcnNotification = 0x00000009 + HcnNotificationNetworkEndpointDetached HcnNotification = 0x00000010 + + // Notifications for HCN_GUESTNETWORKSERVICE handles + HcnNotificationGuestNetworkServiceStateChanged HcnNotification = 0x00000011 + HcnNotificationGuestNetworkServiceInterfaceStateChanged HcnNotification = 0x00000012 + + // Common notifications + HcnNotificationServiceDisconnect HcnNotification = 0x01000000 +) + +type HcnNotification uint32 + +func (hn HcnNotification) String() string { + switch hn { + case HcnNotificationNetworkPreCreate: + return "NetworkPreCreate" + case HcnNotificationNetworkCreate: + return "NetworkCreate" + case HcnNotificationNetworkPreDelete: + return "NetworkPreDelete" + case HcnNotificationNetworkDelete: + return "NetworkDelete" + case HcnNotificationNamespaceCreate: + return "NamespaceCreate" + case HcnNotificationNamespaceDelete: + return "NamespaceDelete" + case HcnNotificationGuestNetworkServiceCreate: + return "GuestNetworkServiceCreate" + case HcnNotificationGuestNetworkServiceDelete: + return "GuestNetworkServiceDelete" + case HcnNotificationNetworkEndpointAttached: + return "NetworkEndpointAttached" + case HcnNotificationNetworkEndpointDetached: + return "NetworkEndpointDetached" + case HcnNotificationGuestNetworkServiceStateChanged: + return "GuestNetworkServiceStateChanged" + case HcnNotificationGuestNetworkServiceInterfaceStateChanged: + return "GuestNetworkServiceInterfaceStateChanged" + case HcnNotificationServiceDisconnect: + return "ServiceDisconnect" + default: + return fmt.Sprintf("Unknown: %d", hn) + } +} + +type HcnNotificationData struct { + Type HcnNotification + Status error + Data NotificationBase +} + +type notificationChannel chan HcnNotificationData + +type notifcationWatcherContext struct { + channel notificationChannel + handle hcnCallbackHandle +} + +func notificationWatcher(notificationType HcnNotification, callbackNumber uintptr, notificationStatus uintptr, notificationData *uint16) uintptr { + var result error + if int32(notificationStatus) < 0 { + result = interop.Win32FromHresult(notificationStatus) + } + + callbackMapLock.RLock() + context := callbackMap[callbackNumber] + callbackMapLock.RUnlock() + + if context == nil { + return 0 + } + + if notificationData == nil { + return 0 + } + + tmpString := syscall.UTF16ToString((*[1 << 29]uint16)(unsafe.Pointer(notificationData))[:]) + + var notifData NotificationBase + err := json.Unmarshal([]byte(tmpString), ¬ifData) + if err == nil { + context.channel <- HcnNotificationData{ + Type: notificationType, + Status: result, + Data: notifData, + } + } + + return 0 +} diff --git a/hcn/hcnerrors.go b/hcn/hcnerrors.go index e7a850b29c..eeeb5608d6 100644 --- a/hcn/hcnerrors.go +++ b/hcn/hcnerrors.go @@ -49,7 +49,7 @@ type NetworkNotFoundError struct { } func (e NetworkNotFoundError) Error() string { - if e.NetworkName == "" { + if e.NetworkName != "" { return fmt.Sprintf("Network Name %s not found", e.NetworkName) } return fmt.Sprintf("Network Id %s not found", e.NetworkID) @@ -62,7 +62,7 @@ type EndpointNotFoundError struct { } func (e EndpointNotFoundError) Error() string { - if e.EndpointName == "" { + if e.EndpointName != "" { return fmt.Sprintf("Endpoint Name %s not found", e.EndpointName) } return fmt.Sprintf("Endpoint Id %s not found", e.EndpointID) diff --git a/hcn/hcnnetwork.go b/hcn/hcnnetwork.go index a95fc1d751..91bfa905f3 100644 --- a/hcn/hcnnetwork.go +++ b/hcn/hcnnetwork.go @@ -3,6 +3,7 @@ package hcn import ( "encoding/json" "errors" + "sync" "github.com/Microsoft/go-winio/pkg/guid" "github.com/Microsoft/hcsshim/internal/interop" @@ -109,6 +110,218 @@ type PolicyNetworkRequest struct { Policies []NetworkPolicy `json:",omitempty"` } +type NotificationBase struct { + Id guid.GUID `json:"ID"` + Flags uint32 `json:",omitempty"` + Data json.RawMessage `json:",omitempty"` +} + +type HcnServiceWatcher struct { + handleLock sync.RWMutex + callbackNumber uintptr + watcherContext *notifcationWatcherContext + started bool +} + +type HcnNetworkWatcher struct { + handleLock sync.RWMutex + callbackNumber uintptr + watcherContext *notifcationWatcherContext + started bool +} + +func NewHcnServiceWatcher() *HcnServiceWatcher { + watcherContext := ¬ifcationWatcherContext{ + channel: make(notificationChannel, 1), + } + + callbackMapLock.Lock() + callbackNumber := nextCallback + nextCallback++ + callbackMap[callbackNumber] = watcherContext + callbackMapLock.Unlock() + + return &HcnServiceWatcher{ + callbackNumber: callbackNumber, + watcherContext: watcherContext, + } +} + +func (w *HcnServiceWatcher) Start() error { + w.handleLock.RLock() + defer w.handleLock.RUnlock() + + if w.started { + return nil + } + + var callbackHandle hcnCallbackHandle + err := hcnRegisterServiceCallback(notificationWatcherCallback, w.callbackNumber, &callbackHandle) + if err != nil { + return err + } + + w.watcherContext.handle = callbackHandle + w.started = true + return nil +} + +func (w *HcnServiceWatcher) Stop() error { + var handle hcnCallbackHandle + + { + w.handleLock.RLock() + defer w.handleLock.RUnlock() + + if !w.started { + return nil + } + w.started = false + handle = w.watcherContext.handle + w.watcherContext.handle = 0 + } + + if handle == 0 { + return nil + } + + // hcnUnregisterServiceCallback has its own syncronization + // to wait for all callbacks to complete. We must NOT hold the callbackMapLock. + err := hcnUnregisterServiceCallback(handle) + if err != nil { + return err + } + + return nil +} + +func (w *HcnServiceWatcher) Notification() <-chan HcnNotificationData { + w.handleLock.RLock() + defer w.handleLock.RUnlock() + + if !w.started { + return nil + } + return w.watcherContext.channel +} + +func (w *HcnServiceWatcher) Close() error { + w.Stop() + + w.handleLock.RLock() + close(w.watcherContext.channel) + callbackNumber := w.callbackNumber + w.handleLock.RUnlock() + + callbackMapLock.Lock() + delete(callbackMap, callbackNumber) + callbackMapLock.Unlock() + + return nil +} + +func NewHcnNetworkWatcher() *HcnNetworkWatcher { + watcherContext := ¬ifcationWatcherContext{ + channel: make(notificationChannel, 1), + } + + callbackMapLock.Lock() + callbackNumber := nextCallback + nextCallback++ + callbackMap[callbackNumber] = watcherContext + callbackMapLock.Unlock() + + return &HcnNetworkWatcher{ + callbackNumber: callbackNumber, + watcherContext: watcherContext, + } +} + +func (w *HcnNetworkWatcher) Start(networkId string) error { + w.handleLock.RLock() + defer w.handleLock.RUnlock() + + if w.started { + return nil + } + + var networkHandle hcnNetwork + var resultBuffer *uint16 + networkGuid, err := guid.FromString(networkId) + if err != nil { + return errInvalidNetworkID + } + + hr := hcnOpenNetwork(&networkGuid, &networkHandle, &resultBuffer) + if err = checkForErrors("hcnOpenNetwork", hr, resultBuffer); err != nil { + return err + } + + var callbackHandle hcnCallbackHandle + err = hcnRegisterNetworkCallback(networkHandle, notificationWatcherCallback, w.callbackNumber, &callbackHandle) + if err != nil { + return err + } + + w.watcherContext.handle = callbackHandle + w.started = true + return nil +} + +func (w *HcnNetworkWatcher) Stop() error { + var handle hcnCallbackHandle + + { + w.handleLock.RLock() + defer w.handleLock.RUnlock() + + if !w.started { + return nil + } + w.started = false + handle = w.watcherContext.handle + w.watcherContext.handle = 0 + } + + if handle == 0 { + return nil + } + + // hcnUnregisterNetworkCallback has its own syncronization + // to wait for all callbacks to complete. We must NOT hold the callbackMapLock. + err := hcnUnregisterNetworkCallback(handle) + if err != nil { + return err + } + + return nil +} + +func (w *HcnNetworkWatcher) Notification() <-chan HcnNotificationData { + w.handleLock.RLock() + defer w.handleLock.RUnlock() + + if !w.started { + return nil + } + return w.watcherContext.channel +} + +func (w *HcnNetworkWatcher) Close() error { + w.Stop() + + w.handleLock.RLock() + close(w.watcherContext.channel) + callbackNumber := w.callbackNumber + w.handleLock.RUnlock() + + callbackMapLock.Lock() + delete(callbackMap, callbackNumber) + callbackMapLock.Unlock() + + return nil +} + func getNetwork(networkGuid guid.GUID, query string) (*HostComputeNetwork, error) { // Open network. var ( @@ -311,15 +524,19 @@ func GetNetworkByID(networkID string) (*HostComputeNetwork, error) { return nil, err } hcnQuery.Filter = string(filter) + queryJson, _ := json.Marshal(hcnQuery) - networks, err := ListNetworksQuery(hcnQuery) + networkGuid, err := guid.FromString(networkID) if err != nil { return nil, err } - if len(networks) == 0 { - return nil, NetworkNotFoundError{NetworkID: networkID} + + network, err := getNetwork(networkGuid, string(queryJson)) + if err != nil { + return nil, err } - return &networks[0], err + + return network, err } // GetNetworkByName returns the network specified by Name. diff --git a/hcn/zsyscall_windows.go b/hcn/zsyscall_windows.go index 856b2c1408..b323dc2f74 100644 --- a/hcn/zsyscall_windows.go +++ b/hcn/zsyscall_windows.go @@ -75,6 +75,8 @@ var ( procHcnRegisterServiceCallback = modcomputenetwork.NewProc("HcnRegisterServiceCallback") procHcnUnregisterServiceCallback = modcomputenetwork.NewProc("HcnUnregisterServiceCallback") procHcnCloseService = modcomputenetwork.NewProc("HcnCloseService") + procHcnRegisterNetworkCallback = modcomputenetwork.NewProc("HcnRegisterNetworkCallback") + procHcnUnregisterNetworkCallback = modcomputenetwork.NewProc("HcnUnregisterNetworkCallback") ) func SetCurrentThreadCompartmentId(compartmentId uint32) (hr error) { @@ -671,11 +673,11 @@ func hcnOpenService(service *hcnService, result **uint16) (hr error) { return } -func hcnRegisterServiceCallback(service hcnService, callback int32, context int32, callbackHandle *hcnCallbackHandle) (hr error) { +func hcnRegisterServiceCallback(callback uintptr, context uintptr, callbackHandle *hcnCallbackHandle) (hr error) { if hr = procHcnRegisterServiceCallback.Find(); hr != nil { return } - r0, _, _ := syscall.Syscall6(procHcnRegisterServiceCallback.Addr(), 4, uintptr(service), uintptr(callback), uintptr(context), uintptr(unsafe.Pointer(callbackHandle)), 0, 0) + r0, _, _ := syscall.Syscall(procHcnRegisterServiceCallback.Addr(), 3, uintptr(callback), uintptr(context), uintptr(unsafe.Pointer(callbackHandle))) if int32(r0) < 0 { if r0&0x1fff0000 == 0x00070000 { r0 &= 0xffff @@ -712,3 +714,31 @@ func hcnCloseService(service hcnService) (hr error) { } return } + +func hcnRegisterNetworkCallback(network hcnNetwork, callback uintptr, context uintptr, callbackHandle *hcnCallbackHandle) (hr error) { + if hr = procHcnRegisterNetworkCallback.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall6(procHcnRegisterNetworkCallback.Addr(), 4, uintptr(network), uintptr(callback), uintptr(context), uintptr(unsafe.Pointer(callbackHandle)), 0, 0) + if int32(r0) < 0 { + if r0&0x1fff0000 == 0x00070000 { + r0 &= 0xffff + } + hr = syscall.Errno(r0) + } + return +} + +func hcnUnregisterNetworkCallback(callbackHandle hcnCallbackHandle) (hr error) { + if hr = procHcnUnregisterNetworkCallback.Find(); hr != nil { + return + } + r0, _, _ := syscall.Syscall(procHcnUnregisterNetworkCallback.Addr(), 1, uintptr(callbackHandle), 0, 0) + if int32(r0) < 0 { + if r0&0x1fff0000 == 0x00070000 { + r0 &= 0xffff + } + hr = syscall.Errno(r0) + } + return +} diff --git a/internal/cow/cow.go b/internal/cow/cow.go index c0158269f0..67d4f9bee3 100644 --- a/internal/cow/cow.go +++ b/internal/cow/cow.go @@ -69,7 +69,7 @@ type Container interface { Start(ctx context.Context) error // Shutdown sends a shutdown request to the container (but does not wait for // the shutdown to complete). - Shutdown(ctx context.Context) error + Shutdown(ctx context.Context, options string) error // Terminate sends a terminate request to the container (but does not wait // for the terminate to complete). Terminate(ctx context.Context) error diff --git a/internal/gcs/container.go b/internal/gcs/container.go index 58a2b65e74..624179cf3a 100644 --- a/internal/gcs/container.go +++ b/internal/gcs/container.go @@ -136,7 +136,7 @@ func (c *Container) shutdown(ctx context.Context, proc rpcProc) error { // Shutdown sends a graceful shutdown request to the container. The container // might not be terminated by the time the request completes (and might never // terminate). -func (c *Container) Shutdown(ctx context.Context) error { +func (c *Container) Shutdown(ctx context.Context, options string) error { return c.shutdown(ctx, rpcShutdownGraceful) } diff --git a/internal/guestrequest/types.go b/internal/guestrequest/types.go index 3dafbdc00a..8ce139cc18 100644 --- a/internal/guestrequest/types.go +++ b/internal/guestrequest/types.go @@ -49,6 +49,10 @@ type LCOWMappedVPMemDevice struct { MountPath string `json:"MountPath,omitempty"` // /tmp/pN } +type LCOWMappedVPCIDevice struct { + VMBusGUID string `json:"VMBusGUID,omitempty"` +} + type LCOWNetworkAdapter struct { NamespaceID string `json:",omitempty"` ID string `json:",omitempty"` @@ -72,6 +76,7 @@ const ( ResourceTypeNetworkNamespace ResourceType = "NetworkNamespace" ResourceTypeCombinedLayers ResourceType = "CombinedLayers" ResourceTypeVPMemDevice ResourceType = "VPMemDevice" + ResourceTypeVPCIDevice ResourceType = "VPCIDevice" ) // GuestRequest is for modify commands passed to the guest. diff --git a/internal/hcs/service.go b/internal/hcs/service.go new file mode 100644 index 0000000000..a306fb0c8e --- /dev/null +++ b/internal/hcs/service.go @@ -0,0 +1,39 @@ +// +build windows + +package hcs + +import ( + "context" + "encoding/json" + + "github.com/Microsoft/hcsshim/internal/interop" + hcsschema "github.com/Microsoft/hcsshim/internal/schema2" +) + +// GetServiceProperties returns properties of the host compute service. +func GetServiceProperties(ctx context.Context, q hcsschema.PropertyQuery) (properties *hcsschema.ServiceProperties, err error) { + operation := "hcs::GetServiceProperties" + + queryb, err := json.Marshal(q) + if err != nil { + return nil, err + } + + var resultp, propertiesp *uint16 + err = hcsGetServiceProperties(string(queryb), &propertiesp, &resultp) + events := processHcsResult(ctx, resultp) + if err != nil { + return nil, &HcsError{Op: operation, Err: err, Events: events} + } + + if propertiesp == nil { + return nil, ErrUnexpectedValue + } + propertiesRaw := interop.ConvertAndFreeCoTaskMemString(propertiesp) + properties = &hcsschema.ServiceProperties{} + if err := json.Unmarshal([]byte(propertiesRaw), properties); err != nil { + return nil, err + } + + return properties, nil +} diff --git a/internal/hcs/system.go b/internal/hcs/system.go index e1cb78a6fb..274b43f5a3 100644 --- a/internal/hcs/system.go +++ b/internal/hcs/system.go @@ -316,7 +316,7 @@ func (computeSystem *System) ID() string { } // Shutdown requests a compute system shutdown. -func (computeSystem *System) Shutdown(ctx gcontext.Context) (err error) { +func (computeSystem *System) Shutdown(ctx gcontext.Context, options string) (err error) { computeSystem.handleLock.RLock() defer computeSystem.handleLock.RUnlock() @@ -331,7 +331,7 @@ func (computeSystem *System) Shutdown(ctx gcontext.Context) (err error) { } var resultp *uint16 - err = hcsShutdownComputeSystemContext(ctx, computeSystem.handle, "", &resultp) + err = hcsShutdownComputeSystemContext(ctx, computeSystem.handle, options, &resultp) events := processHcsResult(ctx, resultp) switch err { case nil, ErrVmcomputeAlreadyStopped, ErrComputeSystemDoesNotExist, ErrVmcomputeOperationPending: diff --git a/internal/requesttype/types.go b/internal/requesttype/types.go index 8c0e1b4555..df2db709b0 100644 --- a/internal/requesttype/types.go +++ b/internal/requesttype/types.go @@ -7,4 +7,5 @@ const ( Add = "Add" Remove = "Remove" PreAdd = "PreAdd" // For networking + Update = "Update" ) diff --git a/internal/schema2/basicinformation.go b/internal/schema2/basicinformation.go new file mode 100644 index 0000000000..2b22ceed58 --- /dev/null +++ b/internal/schema2/basicinformation.go @@ -0,0 +1,14 @@ +/* + * HCS API + * + * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + * + * API version: 2.1 + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package hcsschema + +type BasicInformation struct { + SupportedSchemaVersions []Version `json:"SupportedSchemaVersions,omitempty"` +} diff --git a/internal/schema2/devices.go b/internal/schema2/devices.go index 781a884015..e985d96d22 100644 --- a/internal/schema2/devices.go +++ b/internal/schema2/devices.go @@ -39,4 +39,8 @@ type Devices struct { FlexibleIov map[string]FlexibleIoDevice `json:"FlexibleIov,omitempty"` SharedMemory *SharedMemoryConfiguration `json:"SharedMemory,omitempty"` + + // TODO: This is pre-release support in schema 2.3. Need to add build number + // docs when a public build with this is out. + VirtualPci map[string]VirtualPciDevice `json:",omitempty"` } diff --git a/internal/schema2/gpu_configuration.go b/internal/schema2/gpu_configuration.go new file mode 100644 index 0000000000..f71792a498 --- /dev/null +++ b/internal/schema2/gpu_configuration.go @@ -0,0 +1,24 @@ +/* + * HCS API + * + * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + * + * API version: 2.1 + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package hcsschema + +type GpuConfiguration struct { + + // The mode used to assign GPUs to the guest. + AssignmentMode string `json:"AssignmentMode,omitempty"` + + // This only applies to List mode, and is ignored in other modes. + // In GPU-P, string is GPU device interface, and unit16 is partition id. HCS simply assigns the partition with the input id. + // In GPU-PV, string is GPU device interface, and unit16 is 0xffff. HCS needs to find an available partition to assign. + AssignmentRequest map[string]uint16 `json:"AssignmentRequest,omitempty"` + + // Whether we allow vendor extension. + AllowVendorExtension bool `json:"AllowVendorExtension,omitempty"` +} diff --git a/internal/schema2/memory_2.go b/internal/schema2/memory_2.go index 27d0b8c483..54b8fefd4d 100644 --- a/internal/schema2/memory_2.go +++ b/internal/schema2/memory_2.go @@ -20,6 +20,10 @@ type Memory2 struct { EnableEpf bool `json:"EnableEpf,omitempty"` + HighMmioBaseInMB int32 `json:"HighMmioBaseInMB,omitempty"` + + HighMmioGapInMB int32 `json:"HighMmioGapInMB,omitempty"` + // EnableDeferredCommit is private in the schema. If regenerated need to add back. EnableDeferredCommit bool `json:"EnableDeferredCommit,omitempty"` } diff --git a/internal/schema2/service_properties.go b/internal/schema2/service_properties.go new file mode 100644 index 0000000000..79ed0d32f0 --- /dev/null +++ b/internal/schema2/service_properties.go @@ -0,0 +1,18 @@ +/* + * HCS API + * + * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + * + * API version: 2.4 + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package hcsschema + +import "encoding/json" + +type ServiceProperties struct { + // Changed Properties field to []json.RawMessage from []interface{} to avoid having to + // remarshal sp.Properties[n] and unmarshal into the type(s) we want. + Properties []json.RawMessage `json:"Properties,omitempty"` +} diff --git a/internal/schema2/shutdown_options.go b/internal/schema2/shutdown_options.go new file mode 100644 index 0000000000..7ab7c7bea8 --- /dev/null +++ b/internal/schema2/shutdown_options.go @@ -0,0 +1,25 @@ +/* + * HCS API + * + * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + * + * API version: 2.1 + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package hcsschema + +type ShutdownOptions struct { + + // What kind of mechanism used to perform the shutdown operation + Mechanism string `json:"Mechanism,omitempty"` + + // What is the type of the shutdown operation + Type string `json:"Type,omitempty"` + + // If this shutdown is forceful or not + Force bool `json:"Force,omitempty"` + + // Reason for the shutdown + Reason string `json:"Reason,omitempty"` +} diff --git a/internal/schema2/uefi.go b/internal/schema2/uefi.go index 0e48ece500..fd175ec1a0 100644 --- a/internal/schema2/uefi.go +++ b/internal/schema2/uefi.go @@ -14,6 +14,9 @@ type Uefi struct { SecureBootTemplateId string `json:"SecureBootTemplateId,omitempty"` + // schema version 2.3 + ApplySecureBootTemplate string `json:"ApplySecureBootTemplate,omitempty"` + BootThis *UefiBootEntry `json:"BootThis,omitempty"` Console string `json:"Console,omitempty"` diff --git a/internal/schema2/virtual_pci_device.go b/internal/schema2/virtual_pci_device.go new file mode 100644 index 0000000000..f5e05903c5 --- /dev/null +++ b/internal/schema2/virtual_pci_device.go @@ -0,0 +1,16 @@ +/* + * HCS API + * + * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + * + * API version: 2.3 + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package hcsschema + +// TODO: This is pre-release support in schema 2.3. Need to add build number +// docs when a public build with this is out. +type VirtualPciDevice struct { + Functions []VirtualPciFunction `json:",omitempty"` +} diff --git a/internal/schema2/virtual_pci_function.go b/internal/schema2/virtual_pci_function.go new file mode 100644 index 0000000000..cedb7d18bc --- /dev/null +++ b/internal/schema2/virtual_pci_function.go @@ -0,0 +1,18 @@ +/* + * HCS API + * + * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + * + * API version: 2.3 + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package hcsschema + +// TODO: This is pre-release support in schema 2.3. Need to add build number +// docs when a public build with this is out. +type VirtualPciFunction struct { + DeviceInstancePath string `json:",omitempty"` + + VirtualFunction uint16 `json:",omitempty"` +} diff --git a/service.go b/service.go new file mode 100644 index 0000000000..a0bcda72b5 --- /dev/null +++ b/service.go @@ -0,0 +1,40 @@ +package hcsshim + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/Microsoft/hcsshim/internal/hcs" + hcsschema "github.com/Microsoft/hcsshim/internal/schema2" +) + +func GetHighestSupportedHcsSchemaVersion() (schemaVersion *hcsschema.Version, err error) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) + defer cancel() + + serviceProperties, err := hcs.GetServiceProperties(ctx, hcsschema.PropertyQuery{PropertyTypes: []string{"Basic"}}) + if err != nil { + return nil, fmt.Errorf("failed to retrieve HCS schema version: %s", err) + } + + basicInfo := &hcsschema.BasicInformation{} + if err := json.Unmarshal(serviceProperties.Properties[0], &basicInfo); err != nil { + return nil, fmt.Errorf("failed to unmarshal HCS Schema Version: %s", err) + } + + var highestVersion hcsschema.Version + for _, version := range basicInfo.SupportedSchemaVersions { + if version.Major >= highestVersion.Major { + if (version.Major > highestVersion.Major) || (version.Minor > highestVersion.Minor) { + highestVersion.Major = version.Major + highestVersion.Minor = version.Minor + } + } + } + + schemaVersion = &highestVersion + + return +} diff --git a/vm.go b/vm.go index ced94af3f1..ce7d73cc0a 100644 --- a/vm.go +++ b/vm.go @@ -1,13 +1,72 @@ package hcsshim import ( + "bytes" "context" "encoding/json" + "errors" + "fmt" + "path" + "strings" + "time" + + "github.com/Microsoft/go-winio/pkg/guid" + "github.com/Microsoft/hcsshim/hcn" + "github.com/Microsoft/hcsshim/internal/guestrequest" "github.com/Microsoft/hcsshim/internal/hcs" + "github.com/Microsoft/hcsshim/internal/requesttype" + "github.com/Microsoft/hcsshim/internal/schema1" hcsschema "github.com/Microsoft/hcsshim/internal/schema2" - "time" + "github.com/Microsoft/hcsshim/internal/wclayer" + "github.com/Microsoft/hcsshim/osversion" + "golang.org/x/sys/windows" +) + +type GpuAssignmentMode string + +const ( + GpuAssignmentModeDisabled = GpuAssignmentMode("Disabled") + GpuAssignmentModeDefault = GpuAssignmentMode("Default") + GpuAssignmentModeList = GpuAssignmentMode("List") + GpuAssignmentModeMirror = GpuAssignmentMode("Mirror") ) +type ShutdownMechanism string + +const ( + ShutdownMechanismGuestConnection = ShutdownMechanism("GuestConnection") + ShutdownMechanismIntegrationService = ShutdownMechanism("IntegrationService") +) + +type ShutdownType string + +const ( + ShutdownTypeShutdown = ShutdownType("Shutdown") + ShutdownTypeHibernate = ShutdownType("Hibernate") + ShutdownTypeReboot = ShutdownType("Reboot") +) + +type VirtualMachineOptions struct { + Name string + Id string + VhdPath string + IsoPath string + Owner string + MemoryInMB int32 + ProcessorCount int32 + VnicId string + MacAddress string + UseGuestConnection bool + GuestConnectionUseVsock bool + AllowOvercommit bool + SecureBootEnabled bool + SecureBootTemplateId string + HighMmioBaseInMB int32 + HighMmioGapInMB int32 +} + +const plan9Port = 564 + type VirtualMachineSpec struct { Name string ID string @@ -16,13 +75,21 @@ type VirtualMachineSpec struct { system *hcs.System } -func CreateVirtualMachineSpec(name, id, vhdPath, isoPath, owner string, memoryInMB, processorCount int, vnicId, macAddress string) (*VirtualMachineSpec, error) { +func CreateVirtualMachineSpec(opts *VirtualMachineOptions) (*VirtualMachineSpec, error) { + // Ensure the VM has access, we use opts.Id to create VM + if err := wclayer.GrantVmAccess(opts.Id, opts.VhdPath); err != nil { + return nil, err + } + if err := wclayer.GrantVmAccess(opts.Id, opts.IsoPath); err != nil { + return nil, err + } + + // determine which schema version to use + schemaVersion := getSchemaVersion(opts) + spec := &hcsschema.ComputeSystem{ - Owner: owner, - SchemaVersion: &hcsschema.Version{ - Major: 2, - Minor: 1, - }, + Owner: opts.Owner, + SchemaVersion: &schemaVersion, ShouldTerminateOnLastHandleClosed: true, VirtualMachine: &hcsschema.VirtualMachine{ Chipset: &hcsschema.Chipset{ @@ -36,47 +103,65 @@ func CreateVirtualMachineSpec(name, id, vhdPath, isoPath, owner string, memoryIn }, ComputeTopology: &hcsschema.Topology{ Memory: &hcsschema.Memory2{ - SizeInMB: int32(memoryInMB), + SizeInMB: int32(opts.MemoryInMB), + AllowOvercommit: opts.AllowOvercommit, }, Processor: &hcsschema.Processor2{ - Count: int32(processorCount), + Count: int32(opts.ProcessorCount), }, }, Devices: &hcsschema.Devices{ Scsi: map[string]hcsschema.Scsi{ - "primary": hcsschema.Scsi{ + "primary": { Attachments: map[string]hcsschema.Attachment{ - "0": hcsschema.Attachment{ - Path: vhdPath, + "0": { + Path: opts.VhdPath, Type_: "VirtualDisk", }, - "1": hcsschema.Attachment{ - Path: isoPath, + "1": { + Path: opts.IsoPath, Type_: "Iso", }, }, }, }, NetworkAdapters: map[string]hcsschema.NetworkAdapter{}, + Plan9: &hcsschema.Plan9{}, }, - // GuestConnection: &hcsschema.GuestConnection{ - // UseVsock: true, - // UseConnectedSuspend: true, - //}, }, } - if len(vnicId) > 0 { + if len(opts.VnicId) > 0 { spec.VirtualMachine.Devices.NetworkAdapters["ext"] = hcsschema.NetworkAdapter{ - EndpointId: vnicId, - MacAddress: macAddress, + EndpointId: opts.VnicId, + MacAddress: opts.MacAddress, } } + if opts.UseGuestConnection { + spec.VirtualMachine.GuestConnection = &hcsschema.GuestConnection{ + UseVsock: opts.GuestConnectionUseVsock, + UseConnectedSuspend: true, + } + } + + if opts.SecureBootEnabled { + spec.VirtualMachine.Chipset.Uefi.SecureBootTemplateId = opts.SecureBootTemplateId + spec.VirtualMachine.Chipset.Uefi.ApplySecureBootTemplate = "Apply" + } + + if opts.HighMmioBaseInMB != 0 { + spec.VirtualMachine.ComputeTopology.Memory.HighMmioBaseInMB = opts.HighMmioBaseInMB + } + + if opts.HighMmioGapInMB != 0 { + spec.VirtualMachine.ComputeTopology.Memory.HighMmioGapInMB = opts.HighMmioGapInMB + } + return &VirtualMachineSpec{ spec: spec, - ID: id, - Name: name, + ID: opts.Id, + Name: opts.Name, }, nil } @@ -92,6 +177,26 @@ func getHcsSpec(system *hcs.System) *hcsschema.ComputeSystem { return nil } +func GetVirtualMachineState(id string) string { + properties, err := GetVirtualMachineProperties(id) + if err != nil { + return "" + } + return properties.State +} + +func GetVirtualMachineProperties(id string) (*schema1.ContainerProperties, error) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) + defer cancel() + system, err := hcs.OpenComputeSystem(ctx, id) + if err != nil { + return nil, err + } + defer system.Close() + + return system.Properties(ctx) +} + func GetVirtualMachineSpec(id string) (*VirtualMachineSpec, error) { ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) defer cancel() @@ -189,7 +294,7 @@ func (vm *VirtualMachineSpec) Start() error { } // Stop a Virtual Machine -func (vm *VirtualMachineSpec) Stop() error { +func (vm *VirtualMachineSpec) Stop(force bool) error { ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) defer cancel() system, err := hcs.OpenComputeSystem(ctx, vm.ID) @@ -198,7 +303,12 @@ func (vm *VirtualMachineSpec) Stop() error { } defer system.Close() - return system.Shutdown(ctx) + shutdownOptions, err := generateShutdownOptions(force) + if err != nil { + return err + } + + return system.Shutdown(ctx, shutdownOptions) } // Delete a Virtual Machine @@ -218,6 +328,19 @@ func (vm *VirtualMachineSpec) Delete() error { return system.Terminate(ctx) } +// Wait for a Virtual Machine exits +func (vm *VirtualMachineSpec) Wait() error { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) + defer cancel() + system, err := hcs.OpenComputeSystem(ctx, vm.ID) + if err != nil { + return err + } + defer system.Close() + + return system.Wait() +} + // ExecuteCommand executes a command in the Virtual Machine func (vm *VirtualMachineSpec) ExecuteCommand(command string) error { ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) @@ -231,6 +354,344 @@ func (vm *VirtualMachineSpec) ExecuteCommand(command string) error { return nil } +// escapeArgs makes a Windows-style escaped command line from a set of arguments +func escapeArgs(args []string) string { + escapedArgs := make([]string, len(args)) + for i, a := range args { + escapedArgs[i] = windows.EscapeArg(a) + } + return strings.Join(escapedArgs, " ") +} + +// RunCommand executes a command on the Virtual Machine +func (vm *VirtualMachineSpec) RunCommand(command []string, user string) (exitCode int, output string, errOut string, err error) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) + defer cancel() + + exitCode = 1 + + system, err := hcs.OpenComputeSystem(ctx, vm.ID) + if err != nil { + return exitCode, "", "", err + } + defer system.Close() + + var params *hcsschema.ProcessParameters + switch system.OS() { + case "linux": + params = &hcsschema.ProcessParameters{ + CommandArgs: command, + WorkingDirectory: "/", + User: user, + Environment: map[string]string{"PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, + CreateStdInPipe: false, + CreateStdOutPipe: true, + CreateStdErrPipe: true, + ConsoleSize: []int32{0, 0}, + } + case "windows": + params = &hcsschema.ProcessParameters{ + CommandLine: escapeArgs(command), + WorkingDirectory: `C:\`, + User: user, + CreateStdInPipe: false, + CreateStdOutPipe: true, + CreateStdErrPipe: true, + ConsoleSize: []int32{0, 0}, + } + default: + return exitCode, "", "", ErrNotSupported + } + + process, err := system.CreateProcess(ctx, params) + if err != nil { + return + } + + defer process.Close() + + err = process.Wait() + if err != nil { + return exitCode, "Wait returned error!", "", err + } + + exitCode, err = process.ExitCode() + + _, reader, errReader := process.Stdio() + if reader != nil { + outBuf := new(bytes.Buffer) + outBuf.ReadFrom(reader) + output = strings.TrimSpace(outBuf.String()) + } + + if errReader != nil { + errBuf := new(bytes.Buffer) + errBuf.ReadFrom(errReader) + errOut = strings.TrimSpace(errBuf.String()) + } + + return +} + +func (vm *VirtualMachineSpec) HotAttachEndpoints(endpoints []*hcn.HostComputeEndpoint) (err error) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) + defer cancel() + system, err := hcs.OpenComputeSystem(ctx, vm.ID) + if err != nil { + return err + } + defer system.Close() + + for _, endpoint := range endpoints { + if err = vm.hotAttachEndpoint(ctx, system, endpoint); err != nil { + return err + } + } + return nil +} + +func (vm *VirtualMachineSpec) HotDetachEndpoint(endpoint *hcn.HostComputeEndpoint) (err error) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) + defer cancel() + system, err := hcs.OpenComputeSystem(ctx, vm.ID) + if err != nil { + return err + } + defer system.Close() + + // Hot detach an endpoint from the compute system + request := hcsschema.ModifySettingRequest{ + RequestType: requesttype.Remove, + ResourcePath: path.Join("VirtualMachine/Devices/NetworkAdapters", endpoint.Id), + Settings: hcsschema.NetworkAdapter{ + EndpointId: endpoint.Id, + MacAddress: endpoint.MacAddress, + }, + } + + if err = system.Modify(ctx, request); err != nil { + return err + } + + return nil +} + +func (vm *VirtualMachineSpec) hotAttachEndpoint(ctx context.Context, system *hcs.System, endpoint *hcn.HostComputeEndpoint) (err error) { + // Hot attach an endpoint to the compute system + request := hcsschema.ModifySettingRequest{ + RequestType: requesttype.Add, + ResourcePath: path.Join("VirtualMachine/Devices/NetworkAdapters", endpoint.Id), + Settings: hcsschema.NetworkAdapter{ + EndpointId: endpoint.Id, + MacAddress: endpoint.MacAddress, + }, + } + + if err = system.Modify(ctx, request); err != nil { + return err + } + + return nil +} + +// AddPlan9 adds a Plan9 share to a VirtualMachineSpec. +func (vm *VirtualMachineSpec) AddPlan9(shareName string, hostPath string, uvmPath string, readOnly bool, restrict bool, allowedNames []string) (err error) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) + defer cancel() + + system, err := hcs.OpenComputeSystem(ctx, vm.ID) + if err != nil { + return err + } + defer system.Close() + + if restrict && osversion.Get().Build < 18328 { + return errors.New("single-file mappings are not supported on this build of Windows") + } + if uvmPath == "" { + return fmt.Errorf("uvmPath must be passed to AddPlan9") + } + + // TODO: JTERRY75 - These are marked private in the schema. For now use them + // but when there are public variants we need to switch to them. + const ( + shareFlagsReadOnly int32 = 0x00000001 + shareFlagsLinuxMetadata int32 = 0x00000004 + shareFlagsCaseSensitive int32 = 0x00000008 + shareFlagsRestrictFileAccess int32 = 0x00000080 + ) + + // TODO: JTERRY75 - `shareFlagsCaseSensitive` only works if the Windows + // `hostPath` supports case sensitivity. We need to detect this case before + // forwarding this flag in all cases. + flags := shareFlagsLinuxMetadata // | shareFlagsCaseSensitive + if readOnly { + flags |= shareFlagsReadOnly + } + if restrict { + flags |= shareFlagsRestrictFileAccess + } + + modification := &hcsschema.ModifySettingRequest{ + RequestType: requesttype.Add, + Settings: hcsschema.Plan9Share{ + Name: shareName, + AccessName: shareName, + Path: hostPath, + Port: plan9Port, + Flags: flags, + AllowedFiles: allowedNames, + }, + ResourcePath: fmt.Sprintf("VirtualMachine/Devices/Plan9/Shares"), + GuestRequest: guestrequest.GuestRequest{ + ResourceType: guestrequest.ResourceTypeMappedDirectory, + RequestType: requesttype.Add, + Settings: guestrequest.LCOWMappedDirectory{ + MountPath: uvmPath, + ShareName: shareName, + Port: plan9Port, + ReadOnly: readOnly, + }, + }, + } + + if err := system.Modify(ctx, modification); err != nil { + return err + } + + return nil +} + +func (vm *VirtualMachineSpec) RemovePlan9(shareName string, uvmPath string) (err error) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) + defer cancel() + + system, err := hcs.OpenComputeSystem(ctx, vm.ID) + if err != nil { + return err + } + defer system.Close() + + modification := &hcsschema.ModifySettingRequest{ + RequestType: requesttype.Remove, + Settings: hcsschema.Plan9Share{ + Name: shareName, + AccessName: shareName, + Port: plan9Port, + }, + ResourcePath: fmt.Sprintf("VirtualMachine/Devices/Plan9/Shares"), + GuestRequest: guestrequest.GuestRequest{ + ResourceType: guestrequest.ResourceTypeMappedDirectory, + RequestType: requesttype.Remove, + Settings: guestrequest.LCOWMappedDirectory{ + MountPath: uvmPath, + ShareName: shareName, + Port: plan9Port, + }, + }, + } + if err := system.Modify(ctx, modification); err != nil { + return fmt.Errorf("failed to remove plan9 share %s from %s: %+v: %s", shareName, vm.ID, modification, err) + } + return nil +} + +func (vm *VirtualMachineSpec) UpdateGpuConfiguration(mode GpuAssignmentMode, allowVendorExtension bool, assignments map[string]uint16) (err error) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) + defer cancel() + + system, err := hcs.OpenComputeSystem(ctx, vm.ID) + if err != nil { + return err + } + defer system.Close() + + settings := hcsschema.GpuConfiguration{ + AssignmentMode: string(mode), + AllowVendorExtension: allowVendorExtension, + } + + if len(assignments) != 0 { + settings.AssignmentRequest = assignments + } + + request := hcsschema.ModifySettingRequest{ + RequestType: requesttype.Update, + ResourcePath: "VirtualMachine/ComputeTopology/Gpu", + Settings: settings, + } + + if err := system.Modify(ctx, request); err != nil { + return err + } + + return nil +} + +// Add vPCI device +func (vm *VirtualMachineSpec) AssignDevice(ctx context.Context, deviceID string) (string, error) { + system, err := hcs.OpenComputeSystem(ctx, vm.ID) + if err != nil { + return "", err + } + defer system.Close() + + guid, err := guid.NewV4() + if err != nil { + return "", err + } + + vmBusGUID := guid.String() + targetDevice := hcsschema.VirtualPciDevice{ + Functions: []hcsschema.VirtualPciFunction{ + { + DeviceInstancePath: deviceID, + }, + }, + } + request := &hcsschema.ModifySettingRequest{ + ResourcePath: fmt.Sprintf("VirtualMachine/Devices/VirtualPci/%s", vmBusGUID), + RequestType: requesttype.Add, + Settings: targetDevice, + } + + // for LCOW, we need to make sure that specific paths relating to the + // device exist so they are ready to be used by later + // work in openGCS + request.GuestRequest = guestrequest.GuestRequest{ + ResourceType: guestrequest.ResourceTypeVPCIDevice, + RequestType: requesttype.Add, + Settings: guestrequest.LCOWMappedVPCIDevice{ + VMBusGUID: vmBusGUID, + }, + } + + if err := system.Modify(ctx, request); err != nil { + return "", err + } + + return vmBusGUID, nil +} + +// Removes a vpci device from VirtualMachineSpec +func (vm *VirtualMachineSpec) RemoveDevice(ctx context.Context, vmBusGUID string) error { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) + defer cancel() + + system, err := hcs.OpenComputeSystem(ctx, vm.ID) + if err != nil { + return err + } + + defer system.Close() + + return system.Modify(ctx, &hcsschema.ModifySettingRequest{ + ResourcePath: fmt.Sprintf("VirtualMachine/Devices/VirtualPci/%s", vmBusGUID), + RequestType: requesttype.Remove, + }) + return nil +} + func (vm *VirtualMachineSpec) String() string { jsonString, err := json.Marshal(vm.spec) if err != nil { @@ -239,3 +700,34 @@ func (vm *VirtualMachineSpec) String() string { return string(jsonString) } + +func generateShutdownOptions(force bool) (string, error) { + // TODO: shutdown options only supported at schema version 2.5 and above + // check current schema version on the running system and return empty string if + // running on schema version 2.4 and below + options := hcsschema.ShutdownOptions{ + Mechanism: string(ShutdownMechanismGuestConnection), + Type: string(ShutdownTypeShutdown), + Force: force, + Reason: "Requested shutdown", + } + optionsB, err := json.Marshal(options) + if err != nil { + return "", err + } + return string(optionsB), nil +} + +func getSchemaVersion(opts *VirtualMachineOptions) hcsschema.Version { + if opts.SecureBootEnabled || opts.HighMmioBaseInMB != 0 || opts.HighMmioGapInMB != 0 { + return hcsschema.Version{ + Major: 2, + Minor: 3, + } + } + + return hcsschema.Version{ + Major: 2, + Minor: 1, + } +}