Skip to content

Commit 91b8f07

Browse files
Merge pull request #192 from shiftstack/bz1985015
Bug 1985015: Eliminate instanceCreate volume leak
2 parents 3024c78 + 5a4ba42 commit 91b8f07

File tree

1 file changed

+28
-8
lines changed

1 file changed

+28
-8
lines changed

pkg/cloud/openstack/clients/machineservice.go

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,17 @@ func GetSecurityGroups(is *InstanceService, sg_param []openstackconfigv1.Securit
462462
// If ServerGroupName is nonempty and no server group exists with that name,
463463
// then InstanceCreate creates a server group with that name.
464464
func (is *InstanceService) InstanceCreate(clusterName string, name string, clusterSpec *openstackconfigv1.OpenstackClusterProviderSpec, config *openstackconfigv1.OpenstackProviderSpec, cmd string, keyName string, configClient configclient.ConfigV1Interface) (instance *Instance, err error) {
465+
// server is only non-nil in case of successful server creation.
466+
//
467+
// There are multiple preparation steps in this method, some of which
468+
// create resources (e.g. a volume in case of boot-from-volume with
469+
// "image" source), and all of which have a chance to fail.
470+
//
471+
// This variable is guaranteed to remain nil until server creation is
472+
// successful. Deferred cleanup functions can restore the initial state
473+
// in case of failure, by only acting if "server" is nil.
474+
var server *servers.Server
475+
465476
if config == nil {
466477
return nil, fmt.Errorf("create Options need be specified to create instace")
467478
}
@@ -732,15 +743,21 @@ func (is *InstanceService) InstanceCreate(clusterName string, name string, clust
732743

733744
volumeID = volume.ID
734745

735-
err = volumes.WaitForStatus(is.volumeClient, volumeID, "available", 300)
736-
if err != nil {
737-
klog.Infof("Bootable volume %v creation failed. Removing...", volumeID)
738-
err = volumes.Delete(is.volumeClient, volumeID, volumes.DeleteOpts{}).ExtractErr()
739-
if err != nil {
740-
return nil, fmt.Errorf("Bootable volume deletion err: %v", err)
746+
defer func() {
747+
// If the server is created in Nova, the lifetime of the associated volume will be
748+
// bound to it. This deferred cleanup function tackles the case where a volume was created
749+
// but never attached to a server. In that case, the volume must be removed manually.
750+
if server == nil {
751+
if err := volumes.Delete(is.volumeClient, volumeID, nil).ExtractErr(); err != nil {
752+
klog.Infof("Failed to delete stale volume %q", volumeID)
753+
} else {
754+
klog.Infof("Deleted stale volume %q", volumeID)
755+
}
741756
}
757+
}()
742758

743-
return nil, fmt.Errorf("Bootable volume %v is not available err: %v", volumeID, err)
759+
if err := volumes.WaitForStatus(is.volumeClient, volumeID, "available", 300); err != nil {
760+
return nil, fmt.Errorf("bootable volume is not available: %v", err)
744761
}
745762

746763
klog.Infof("Bootable volume %v was created successfully.", volumeID)
@@ -815,11 +832,14 @@ func (is *InstanceService) InstanceCreate(clusterName string, name string, clust
815832
}
816833
}
817834

818-
server, err := servers.Create(is.computeClient, keypairs.CreateOptsExt{
835+
server, err = servers.Create(is.computeClient, keypairs.CreateOptsExt{
819836
CreateOptsBuilder: serverCreateOpts,
820837
KeyName: keyName,
821838
}).Extract()
822839
if err != nil {
840+
// Gophercloud does not guarantee "server" to be nil when "err"
841+
// is not nil. Resetting to nil to trigger cleanup functions.
842+
server = nil
823843
return nil, fmt.Errorf("Create new server err: %v", err)
824844
}
825845

0 commit comments

Comments
 (0)