@@ -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.
464464func (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