From 40fd7a60635ee24ee7619d7d647fbb48d8e3323f Mon Sep 17 00:00:00 2001 From: Amey Gawde Date: Thu, 30 Oct 2025 02:28:58 +0000 Subject: [PATCH 1/2] Adding Poweroff option to allow graceful shutdown. --- services/compute/virtualmachine/client.go | 12 ++++++- .../compute/virtualmachine/internal/wssd.go | 32 +++++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/services/compute/virtualmachine/client.go b/services/compute/virtualmachine/client.go index e594727f..4aff388f 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 @@ -73,6 +74,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 { @@ -313,4 +323,4 @@ func isDifferentGpuList(oldGpuList, newGpuList []*compute.VirtualMachineGPU) boo func (c *VirtualMachineClient) GetHyperVVmId(ctx context.Context, group string, name string) (*compute.VirtualMachineHyperVVmId, error) { return c.internal.GetHyperVVmId(ctx, group, name) -} \ No newline at end of file +} diff --git a/services/compute/virtualmachine/internal/wssd.go b/services/compute/virtualmachine/internal/wssd.go index d1e03543..dea0cb67 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 { @@ -331,7 +359,7 @@ func (c *client) GetHyperVVmId(ctx context.Context, group, name string) (*comput if len(vm) == 0 { return nil, fmt.Errorf("Virtual Machine [%s] not found", name) } - + mocResponse, err := c.VirtualMachineAgentClient.GetHyperVVmId(ctx, vm[0]) if err != nil { return nil, err @@ -341,4 +369,4 @@ func (c *client) GetHyperVVmId(ctx context.Context, group, name string) (*comput } return response, nil -} \ No newline at end of file +} From 5f9c21e7046c9a6593c59b94af14185d9427fda4 Mon Sep 17 00:00:00 2001 From: Amey Gawde Date: Thu, 30 Oct 2025 20:40:13 +0000 Subject: [PATCH 2/2] adding missing files --- go.mod | 3 ++- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index e44a0cef..80b10e78 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( golang.org/x/net v0.44.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/text v0.30.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v2 v2.3.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect @@ -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 ba57f19c..d9ceb94d 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.4 h1:TZ3tf/qCa23Ob7YLKTLJS85RUktB8UfYSjuusTMlVwE= -github.com/microsoft/moc v0.35.4/go.mod h1:Z19DHc5ziqJ8BYKOw3RksDBH7vrdvxPw0PHrG+MzCtA= 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= @@ -203,8 +203,8 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f h1:1FTH6cpXFsENbPR5Bu8NQddPSaUUE6NA2XdZdDSAJK4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=