Skip to content

Commit 47154ea

Browse files
committed
Shutdown VMs before Deletion
Tries to shutdown the OpenStack VM before deleting it. This way even Pods form Daemonsets are shut down more gracefully and services like license daemons on the VMs can be properly shutdown. Related to #1973
1 parent ee4be02 commit 47154ea

File tree

5 files changed

+67
-0
lines changed

5 files changed

+67
-0
lines changed

api/v1beta1/conditions_consts.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ const (
3838
InstanceDeletedReason = "InstanceDeleted"
3939
// InstanceNotReadyReason used when the instance is in a pending state.
4040
InstanceNotReadyReason = "InstanceNotReady"
41+
// InstanceStopFailedReason used when stopping the instance failed.
42+
InstanceStopFailedReason = "InstanceStopFailed"
4143
// InstanceDeleteFailedReason used when deleting the instance failed.
4244
InstanceDeleteFailedReason = "InstanceDeleteFailed"
4345
// OpenstackErrorReason used when there is an error communicating with OpenStack.

controllers/openstackserver_controller.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,14 @@ func (r *OpenStackServerReconciler) reconcileDelete(scope *scope.WithLogger, ope
257257
return fmt.Errorf("delete volumes: %w", err)
258258
}
259259
} else {
260+
// Try to stop the VM, if active before deletion
261+
if instanceStatus.State() == infrav1.InstanceStateActive {
262+
if err := computeService.StopInstance(openStackServer, instanceStatus); err != nil {
263+
v1beta1conditions.MarkFalse(openStackServer, infrav1.InstanceReadyCondition, infrav1.InstanceStopFailedReason, clusterv1beta1.ConditionSeverityError, "Stopping instance failed: %v", err)
264+
return fmt.Errorf("stop instance: %w", err)
265+
}
266+
}
267+
260268
if err := computeService.DeleteInstance(openStackServer, instanceStatus); err != nil {
261269
v1beta1conditions.MarkFalse(openStackServer, infrav1.InstanceReadyCondition, infrav1.InstanceDeleteFailedReason, clusterv1beta1.ConditionSeverityError, "Deleting instance failed: %v", err)
262270
return fmt.Errorf("delete instance: %w", err)

pkg/clients/compute.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ type ComputeClient interface {
6161
ListFlavors() ([]flavors.Flavor, error)
6262
CreateServer(createOpts servers.CreateOptsBuilder, schedulerHints servers.SchedulerHintOptsBuilder) (*servers.Server, error)
6363
DeleteServer(serverID string) error
64+
StopServer(serverID string) error
6465
GetServer(serverID string) (*servers.Server, error)
6566
ListServers(listOpts servers.ListOptsBuilder) ([]servers.Server, error)
6667

@@ -141,6 +142,12 @@ func (c computeClient) DeleteServer(serverID string) error {
141142
return mc.ObserveRequestIgnoreNotFound(err)
142143
}
143144

145+
func (c computeClient) StopServer(serverID string) error {
146+
mc := metrics.NewMetricPrometheusContext("server", "stop")
147+
err := servers.Stop(context.TODO(), c.client, serverID).ExtractErr()
148+
return mc.ObserveRequestIgnoreNotFound(err)
149+
}
150+
144151
func (c computeClient) GetServer(serverID string) (*servers.Server, error) {
145152
var server servers.Server
146153
mc := metrics.NewMetricPrometheusContext("server", "get")
@@ -230,6 +237,10 @@ func (e computeErrorClient) DeleteServer(_ string) error {
230237
return e.error
231238
}
232239

240+
func (e computeErrorClient) StopServer(_ string) error {
241+
return e.error
242+
}
243+
233244
func (e computeErrorClient) GetServer(_ string) (*servers.Server, error) {
234245
return nil, e.error
235246
}

pkg/clients/mock/compute.go

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/cloud/services/compute/instance.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import (
4545
const (
4646
retryIntervalInstanceStatus = 10 * time.Second
4747
timeoutInstanceCreate = 5
48+
timeoutInstanceStop = 5 * time.Minute
4849
timeoutInstanceDelete = 5 * time.Minute
4950
)
5051

@@ -560,6 +561,37 @@ func (s *Service) deleteVolume(instanceName string, nameSuffix string) error {
560561
return s.getVolumeClient().DeleteVolume(volume.ID, volumes.DeleteOpts{})
561562
}
562563

564+
func (s *Service) StopInstance(eventObject runtime.Object, instanceStatus *InstanceStatus) error {
565+
instance := instanceStatus.InstanceIdentifier()
566+
err := s.getComputeClient().StopServer(instance.ID)
567+
if err != nil {
568+
if capoerrors.IsNotFound(err) {
569+
record.Eventf(eventObject, "SuccessfulStopServer", "Server %s with id %s did not exist", instance.Name, instance.ID)
570+
return nil
571+
}
572+
record.Warnf(eventObject, "FailedStopServer", "Failed to stop server %s with id %s: %v", instance.Name, instance.ID, err)
573+
return err
574+
}
575+
err = wait.PollUntilContextTimeout(context.TODO(), retryIntervalInstanceStatus, timeoutInstanceStop, true, func(_ context.Context) (bool, error) {
576+
i, err := s.GetInstanceStatus(instance.ID)
577+
if err != nil {
578+
return false, err
579+
}
580+
// a little bit noisy, but since the code is new, I guess this is a little bit easier for debugging purposes
581+
record.Eventf(eventObject, "Server State", "server %s with id %s has state %v", instance.Name, instance.ID, i.State())
582+
if i.State() == infrav1.InstanceStateShutoff {
583+
return true, nil
584+
}
585+
return false, nil
586+
})
587+
if err != nil {
588+
record.Warnf(eventObject, "FailedStopServer", "Failed to stop server %s with id %s: %v", instance.Name, instance.ID, err)
589+
return err
590+
}
591+
record.Eventf(eventObject, "SuccessfulStopServer", "Stopped server %s with id %s", instance.Name, instance.ID)
592+
return nil
593+
}
594+
563595
func (s *Service) GetInstanceStatus(resourceID string) (instance *InstanceStatus, err error) {
564596
if resourceID == "" {
565597
return nil, fmt.Errorf("resourceId should be specified to get detail")

0 commit comments

Comments
 (0)