github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/vsphere/environ.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package vsphere 5 6 import ( 7 "path" 8 "sync" 9 10 "github.com/juju/errors" 11 "github.com/juju/version/v2" 12 "github.com/vmware/govmomi/vim25/mo" 13 "golang.org/x/net/context" 14 15 "github.com/juju/juju/core/instance" 16 "github.com/juju/juju/environs" 17 environscloudspec "github.com/juju/juju/environs/cloudspec" 18 "github.com/juju/juju/environs/config" 19 callcontext "github.com/juju/juju/environs/context" 20 "github.com/juju/juju/provider/common" 21 ) 22 23 // Note: This provider/environment does *not* implement storage. 24 25 type environ struct { 26 name string 27 cloud environscloudspec.CloudSpec 28 provider *environProvider 29 30 // namespace is used to create the machine and device hostnames. 31 namespace instance.Namespace 32 33 lock sync.Mutex // lock protects access the following fields. 34 ecfg *environConfig 35 } 36 37 func newEnviron( 38 provider *environProvider, 39 cloud environscloudspec.CloudSpec, 40 cfg *config.Config, 41 ) (*environ, error) { 42 ecfg, err := newValidConfig(cfg) 43 if err != nil { 44 return nil, errors.Annotate(err, "invalid config") 45 } 46 47 namespace, err := instance.NewNamespace(cfg.UUID()) 48 if err != nil { 49 return nil, errors.Trace(err) 50 } 51 52 env := &environ{ 53 name: ecfg.Name(), 54 cloud: cloud, 55 provider: provider, 56 ecfg: ecfg, 57 namespace: namespace, 58 } 59 return env, nil 60 } 61 62 func (env *environ) withClient(ctx context.Context, callCtx callcontext.ProviderCallContext, f func(Client) error) error { 63 client, err := env.dialClient(ctx) 64 if err != nil { 65 // LP #1849194: this is a case at bootstrap time, where a connection 66 // to vsphere failed. It can be wrong Credentials only, differently 67 // from all the other HandleCredentialError cases 68 common.HandleCredentialError(IsAuthorisationFailure, err, callCtx) 69 return errors.Annotate(err, "dialing client") 70 } 71 defer client.Close(ctx) 72 return f(client) 73 } 74 75 func (env *environ) dialClient(ctx context.Context) (Client, error) { 76 return dialClient(ctx, env.cloud, env.provider.dial) 77 } 78 79 // Name is part of the environs.Environ interface. 80 func (env *environ) Name() string { 81 return env.name 82 } 83 84 // Provider is part of the environs.Environ interface. 85 func (env *environ) Provider() environs.EnvironProvider { 86 return env.provider 87 } 88 89 // SetConfig is part of the environs.Environ interface. 90 func (env *environ) SetConfig(cfg *config.Config) error { 91 env.lock.Lock() 92 defer env.lock.Unlock() 93 94 if env.ecfg == nil { 95 return errors.New("cannot set config on uninitialized env") 96 } 97 98 if err := env.ecfg.update(cfg); err != nil { 99 return errors.Annotate(err, "invalid config change") 100 } 101 return nil 102 } 103 104 // Config is part of the environs.Environ interface. 105 func (env *environ) Config() *config.Config { 106 env.lock.Lock() 107 cfg := env.ecfg.Config 108 env.lock.Unlock() 109 return cfg 110 } 111 112 // PrepareForBootstrap implements environs.Environ. 113 func (env *environ) PrepareForBootstrap(ctx environs.BootstrapContext, controllerName string) error { 114 return nil 115 } 116 117 // Create implements environs.Environ. 118 func (env *environ) Create(ctx callcontext.ProviderCallContext, args environs.CreateParams) error { 119 return env.withSession(ctx, func(env *sessionEnviron) error { 120 return env.Create(ctx, args) 121 }) 122 } 123 124 // Create implements environs.Environ. 125 func (env *sessionEnviron) Create(ctx callcontext.ProviderCallContext, args environs.CreateParams) error { 126 return env.ensureVMFolder(args.ControllerUUID, ctx) 127 } 128 129 // Bootstrap is exported, because it has to be rewritten in external unit tests 130 var Bootstrap = common.Bootstrap 131 132 // Bootstrap is part of the environs.Environ interface. 133 func (env *environ) Bootstrap( 134 ctx environs.BootstrapContext, 135 callCtx callcontext.ProviderCallContext, 136 args environs.BootstrapParams, 137 ) (result *environs.BootstrapResult, err error) { 138 // NOTE(axw) we must not pass a sessionEnviron to common.Bootstrap, 139 // as the Environ will be used during instance finalization after 140 // the Bootstrap method returns, and the session will be invalid. 141 if err := env.withSession(callCtx, func(env *sessionEnviron) error { 142 return env.ensureVMFolder(args.ControllerConfig.ControllerUUID(), callCtx) 143 }); err != nil { 144 return nil, errors.Trace(err) 145 } 146 return Bootstrap(ctx, env, callCtx, args) 147 } 148 149 func (env *sessionEnviron) Bootstrap( 150 ctx environs.BootstrapContext, 151 callCtx callcontext.ProviderCallContext, 152 args environs.BootstrapParams, 153 ) (result *environs.BootstrapResult, err error) { 154 return nil, errors.Errorf("sessionEnviron.Bootstrap should never be called") 155 } 156 157 func (env *sessionEnviron) ensureVMFolder(controllerUUID string, ctx callcontext.ProviderCallContext) error { 158 _, err := env.client.EnsureVMFolder(env.ctx, env.getVMFolder(), path.Join( 159 controllerFolderName(controllerUUID), 160 env.modelFolderName(), 161 )) 162 HandleCredentialError(err, env, ctx) 163 return errors.Trace(err) 164 } 165 166 // DestroyEnv is exported, because it has to be rewritten in external unit tests. 167 var DestroyEnv = common.Destroy 168 169 // AdoptResources is part of the Environ interface. 170 func (env *environ) AdoptResources(ctx callcontext.ProviderCallContext, controllerUUID string, fromVersion version.Number) error { 171 // Move model folder into the controller's folder. 172 return env.withSession(ctx, func(env *sessionEnviron) error { 173 return env.AdoptResources(ctx, controllerUUID, fromVersion) 174 }) 175 } 176 177 // AdoptResources is part of the Environ interface. 178 func (env *sessionEnviron) AdoptResources(ctx callcontext.ProviderCallContext, controllerUUID string, fromVersion version.Number) error { 179 err := env.client.MoveVMFolderInto(env.ctx, 180 path.Join(env.getVMFolder(), controllerFolderName(controllerUUID)), 181 path.Join(env.getVMFolder(), controllerFolderName("*"), env.modelFolderName()), 182 ) 183 HandleCredentialError(err, env, ctx) 184 return err 185 } 186 187 // Destroy is part of the environs.Environ interface. 188 func (env *environ) Destroy(ctx callcontext.ProviderCallContext) error { 189 return env.withSession(ctx, func(env *sessionEnviron) error { 190 return env.Destroy(ctx) 191 }) 192 } 193 194 // Destroy is part of the environs.Environ interface. 195 func (env *sessionEnviron) Destroy(ctx callcontext.ProviderCallContext) error { 196 if err := DestroyEnv(env, ctx); err != nil { 197 // We don't need to worry about handling credential errors 198 // here - this is implemented in terms of common operations 199 // that call back into this provider, so we'll handle them 200 // further down the stack. 201 return errors.Trace(err) 202 } 203 err := env.client.DestroyVMFolder(env.ctx, 204 path.Join(env.getVMFolder(), controllerFolderName("*"), env.modelFolderName()), 205 ) 206 HandleCredentialError(err, env, ctx) 207 return err 208 } 209 210 // DestroyController implements the Environ interface. 211 func (env *environ) DestroyController(ctx callcontext.ProviderCallContext, controllerUUID string) error { 212 return env.withSession(ctx, func(env *sessionEnviron) error { 213 return env.DestroyController(ctx, controllerUUID) 214 }) 215 } 216 217 // DestroyController implements the Environ interface. 218 func (env *sessionEnviron) DestroyController(ctx callcontext.ProviderCallContext, controllerUUID string) error { 219 if err := env.Destroy(ctx); err != nil { 220 return errors.Trace(err) 221 } 222 controllerFolderName := controllerFolderName(controllerUUID) 223 if err := env.client.RemoveVirtualMachines(env.ctx, 224 path.Join(env.getVMFolder(), controllerFolderName, modelFolderName("*", "*"), "*"), 225 ); err != nil { 226 HandleCredentialError(err, env, ctx) 227 return errors.Annotate(err, "removing VMs") 228 } 229 if err := env.client.DestroyVMFolder(env.ctx, path.Join(env.getVMFolder(), controllerFolderName)); err != nil { 230 HandleCredentialError(err, env, ctx) 231 return errors.Annotate(err, "destroying VM folder") 232 } 233 return nil 234 } 235 236 func (env *sessionEnviron) getVMFolder() string { 237 return env.environ.cloud.Credential.Attributes()[credAttrVMFolder] 238 } 239 240 func (env *sessionEnviron) accessibleDatastores(ctx callcontext.ProviderCallContext) ([]mo.Datastore, error) { 241 datastores, err := env.client.Datastores(env.ctx) 242 if err != nil { 243 HandleCredentialError(err, env, ctx) 244 return nil, errors.Trace(err) 245 } 246 var results []mo.Datastore 247 for _, ds := range datastores { 248 if !ds.Summary.Accessible { 249 continue 250 } 251 results = append(results, ds) 252 } 253 return results, nil 254 }