diff --git a/go.mod b/go.mod index a73b2698..4ce95eb5 100644 --- a/go.mod +++ b/go.mod @@ -45,6 +45,7 @@ require ( replace ( github.com/gogo/protobuf => github.com/gogo/protobuf v1.3.2 + github.com/microsoft/moc => github.com/arg5739/moc v0.37.1-0.20251030202049-256cd7317066 github.com/miekg/dns => github.com/miekg/dns v1.1.25 golang.org/x/crypto => golang.org/x/crypto v0.37.0 golang.org/x/image => golang.org/x/image v0.23.0 diff --git a/go.sum b/go.sum index 6b72d0e2..9c446001 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ code.cloudfoundry.org/bytefmt v0.53.0/go.mod h1:q/Fu9SYbYx4sZLJSrJxipQYJUsyx8wpF github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/arg5739/moc v0.37.1-0.20251030202049-256cd7317066 h1:8y05U5bVlk8l0ev/N97p5/t6f0SVOTkZ8NPFamFf1ok= +github.com/arg5739/moc v0.37.1-0.20251030202049-256cd7317066/go.mod h1:ZCe9fB0fehR5q+xKWVq7uNhvx+mAuHKFz4srsUVp/P8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -72,8 +74,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/microsoft/moc v0.35.5 h1:3Kl83HXK0iAP6B1SMSSypBK75S+pwbuzJPrjUqwzkUk= -github.com/microsoft/moc v0.35.5/go.mod h1:lF4GKU8OLgqWmOCmu7qLXc/CAqY1Svr5TNsSRLqbt+U= github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw= github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= diff --git a/services/compute/virtualmachine/client.go b/services/compute/virtualmachine/client.go index dc13b333..d890f5ea 100644 --- a/services/compute/virtualmachine/client.go +++ b/services/compute/virtualmachine/client.go @@ -22,6 +22,7 @@ type Service interface { Hydrate(context.Context, string, string, *compute.VirtualMachine) (*compute.VirtualMachine, error) Start(context.Context, string, string) error Stop(context.Context, string, string) error + Poweroff(context.Context, string, string, bool) error Pause(context.Context, string, string) error Save(context.Context, string, string) error RemoveIsoDisk(context.Context, string, string) error @@ -74,6 +75,15 @@ func (c *VirtualMachineClient) Stop(ctx context.Context, group string, name stri err = c.internal.Stop(ctx, group, name) return } + +// Poweroff initiates a VM shutdown operation +// skipShutdown: false (default/recommended) = graceful shutdown with guest OS notification +// +// true = force immediate shutdown (not recommended, may cause data loss) +func (c *VirtualMachineClient) Poweroff(ctx context.Context, group string, name string, skipShutdown bool) (err error) { + err = c.internal.Poweroff(ctx, group, name, skipShutdown) + return +} func (c *VirtualMachineClient) Restart(ctx context.Context, group string, name string) (err error) { err = c.internal.Stop(ctx, group, name) if err != nil { diff --git a/services/compute/virtualmachine/internal/wssd.go b/services/compute/virtualmachine/internal/wssd.go index 7b611fcc..3c74ce49 100644 --- a/services/compute/virtualmachine/internal/wssd.go +++ b/services/compute/virtualmachine/internal/wssd.go @@ -132,6 +132,17 @@ func (c *client) Stop(ctx context.Context, group, name string) (err error) { return } +// Poweroff performs a graceful shutdown of the VM +// skipShutdown: false (default) = graceful shutdown, true = force immediate shutdown +func (c *client) Poweroff(ctx context.Context, group, name string, skipShutdown bool) (err error) { + request, err := c.getVirtualMachineOperationRequestForPowerOff(ctx, wssdcommonproto.VirtualMachineOperation_STOP_GRACEFUL, name, skipShutdown) + if err != nil { + return + } + _, err = c.VirtualMachineAgentClient.Operate(ctx, request) + return +} + func (c *client) Pause(ctx context.Context, group, name string) (err error) { request, err := c.getVirtualMachineOperationRequest(ctx, wssdcommonproto.VirtualMachineOperation_PAUSE, name) if err != nil { @@ -240,6 +251,23 @@ func (c *client) getVirtualMachineOperationRequest(ctx context.Context, opType w return } +func (c *client) getVirtualMachineOperationRequestForPowerOff(ctx context.Context, opType wssdcommonproto.VirtualMachineOperation, name string, skipShutdown bool) (request *wssdcompute.VirtualMachineOperationRequest, err error) { + vms, err := c.get(ctx, "", name) + if err != nil { + return + } + + // skipShutdown = false (default): graceful shutdown with guest OS notification + // skipShutdown = true: immediate force shutdown + request = &wssdcompute.VirtualMachineOperationRequest{ + OperationType: opType, + VirtualMachines: vms, + SkipShutdown: skipShutdown, // Defaults to false when not explicitly set + } + + return +} + func (c *client) getVirtualMachineRunCommandRequest(ctx context.Context, group, name string, request *compute.VirtualMachineRunCommandRequest) (mocRequest *wssdcompute.VirtualMachineRunCommandRequest, err error) { vms, err := c.get(ctx, group, name) if err != nil {