github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/lxd/environ.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lxd 5 6 import ( 7 "strings" 8 "sync" 9 10 "github.com/juju/errors" 11 "github.com/lxc/lxd/shared/api" 12 "gopkg.in/juju/charm.v6" 13 14 "github.com/juju/juju/core/instance" 15 "github.com/juju/juju/environs" 16 "github.com/juju/juju/environs/config" 17 "github.com/juju/juju/environs/context" 18 "github.com/juju/juju/environs/tags" 19 "github.com/juju/juju/provider/common" 20 ) 21 22 const bootstrapMessage = `To configure your system to better support LXD containers, please see: https://github.com/lxc/lxd/blob/master/doc/production-setup.md` 23 24 type baseProvider interface { 25 // BootstrapEnv bootstraps a Juju environment. 26 BootstrapEnv(environs.BootstrapContext, context.ProviderCallContext, environs.BootstrapParams) (*environs.BootstrapResult, error) 27 28 // DestroyEnv destroys the provided Juju environment. 29 DestroyEnv(ctx context.ProviderCallContext) error 30 } 31 32 type environ struct { 33 cloud environs.CloudSpec 34 provider *environProvider 35 36 name string 37 uuid string 38 server Server 39 base baseProvider 40 41 // namespace is used to create the machine and device hostnames. 42 namespace instance.Namespace 43 44 lock sync.Mutex 45 ecfg *environConfig 46 } 47 48 func newEnviron( 49 _ *environProvider, 50 spec environs.CloudSpec, 51 cfg *config.Config, 52 serverFactory ServerFactory, 53 ) (*environ, error) { 54 ecfg, err := newValidConfig(cfg) 55 if err != nil { 56 return nil, errors.Annotate(err, "invalid config") 57 } 58 59 namespace, err := instance.NewNamespace(cfg.UUID()) 60 if err != nil { 61 return nil, errors.Trace(err) 62 } 63 64 server, err := serverFactory.RemoteServer(spec) 65 if err != nil { 66 return nil, errors.Trace(err) 67 } 68 69 env := &environ{ 70 cloud: spec, 71 name: ecfg.Name(), 72 uuid: ecfg.UUID(), 73 server: server, 74 namespace: namespace, 75 ecfg: ecfg, 76 } 77 env.base = common.DefaultProvider{Env: env} 78 79 if err := env.initProfile(); err != nil { 80 return nil, errors.Trace(err) 81 } 82 83 return env, nil 84 } 85 86 func (env *environ) initProfile() error { 87 pName := env.profileName() 88 89 hasProfile, err := env.server.HasProfile(pName) 90 if err != nil { 91 return errors.Trace(err) 92 } 93 if hasProfile { 94 return nil 95 } 96 97 cfg := map[string]string{ 98 "boot.autostart": "true", 99 "security.nesting": "true", 100 } 101 return env.server.CreateProfileWithConfig(pName, cfg) 102 } 103 104 func (env *environ) profileName() string { 105 return "juju-" + env.Name() 106 } 107 108 // Name returns the name of the environ. 109 func (env *environ) Name() string { 110 return env.name 111 } 112 113 // Provider returns the provider that created this environ. 114 func (env *environ) Provider() environs.EnvironProvider { 115 return env.provider 116 } 117 118 // SetConfig updates the environ's configuration. 119 func (env *environ) SetConfig(cfg *config.Config) error { 120 env.lock.Lock() 121 defer env.lock.Unlock() 122 ecfg, err := newValidConfig(cfg) 123 if err != nil { 124 return errors.Trace(err) 125 } 126 env.ecfg = ecfg 127 return nil 128 } 129 130 // Config returns the configuration data with which the env was created. 131 func (env *environ) Config() *config.Config { 132 env.lock.Lock() 133 cfg := env.ecfg.Config 134 env.lock.Unlock() 135 return cfg 136 } 137 138 // PrepareForBootstrap implements environs.Environ. 139 func (env *environ) PrepareForBootstrap(ctx environs.BootstrapContext) error { 140 return nil 141 } 142 143 // Create implements environs.Environ. 144 func (env *environ) Create(context.ProviderCallContext, environs.CreateParams) error { 145 return nil 146 } 147 148 // Bootstrap implements environs.Environ. 149 func (env *environ) Bootstrap(ctx environs.BootstrapContext, callCtx context.ProviderCallContext, params environs.BootstrapParams) (*environs.BootstrapResult, error) { 150 ctx.Infof("%s", bootstrapMessage) 151 return env.base.BootstrapEnv(ctx, callCtx, params) 152 } 153 154 // Destroy shuts down all known machines and destroys the rest of the 155 // known environment. 156 func (env *environ) Destroy(ctx context.ProviderCallContext) error { 157 if err := env.base.DestroyEnv(ctx); err != nil { 158 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 159 return errors.Trace(err) 160 } 161 if env.storageSupported() { 162 if err := destroyModelFilesystems(env); err != nil { 163 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 164 return errors.Annotate(err, "destroying LXD filesystems for model") 165 } 166 } 167 return nil 168 } 169 170 // DestroyController implements the Environ interface. 171 func (env *environ) DestroyController(ctx context.ProviderCallContext, controllerUUID string) error { 172 if err := env.Destroy(ctx); err != nil { 173 return errors.Trace(err) 174 } 175 if err := env.destroyHostedModelResources(controllerUUID); err != nil { 176 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 177 return errors.Trace(err) 178 } 179 if env.storageSupported() { 180 if err := destroyControllerFilesystems(env, controllerUUID); err != nil { 181 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 182 return errors.Annotate(err, "destroying LXD filesystems for controller") 183 } 184 } 185 return nil 186 } 187 188 func (env *environ) destroyHostedModelResources(controllerUUID string) error { 189 // Destroy all instances with juju-controller-uuid 190 // matching the specified UUID. 191 const prefix = "juju-" 192 instances, err := env.prefixedInstances(prefix) 193 if err != nil { 194 return errors.Annotate(err, "listing instances") 195 } 196 197 var names []string 198 for _, inst := range instances { 199 if inst.container.Metadata(tags.JujuModel) == env.uuid { 200 continue 201 } 202 if inst.container.Metadata(tags.JujuController) != controllerUUID { 203 continue 204 } 205 names = append(names, string(inst.Id())) 206 } 207 logger.Debugf("removing instances: %v", names) 208 209 return errors.Trace(env.server.RemoveContainers(names)) 210 } 211 212 // lxdAvailabilityZone wraps a LXD cluster member as an availability zone. 213 type lxdAvailabilityZone struct { 214 api.ClusterMember 215 } 216 217 // Name implements AvailabilityZone. 218 func (z *lxdAvailabilityZone) Name() string { 219 return z.ServerName 220 } 221 222 // Available implements AvailabilityZone. 223 func (z *lxdAvailabilityZone) Available() bool { 224 return strings.ToLower(z.Status) == "online" 225 } 226 227 // AvailabilityZones (ZonedEnviron) returns all availability zones in the 228 // environment. For LXD, this means the cluster node names. 229 func (env *environ) AvailabilityZones(ctx context.ProviderCallContext) ([]common.AvailabilityZone, error) { 230 // If we are not using a clustered server (which includes those not 231 // supporting the clustering API) just represent the single server as the 232 // only availability zone. 233 if !env.server.IsClustered() { 234 return []common.AvailabilityZone{ 235 &lxdAvailabilityZone{ 236 ClusterMember: api.ClusterMember{ 237 ServerName: env.server.Name(), 238 Status: "ONLINE", 239 }, 240 }, 241 }, nil 242 } 243 244 nodes, err := env.server.GetClusterMembers() 245 if err != nil { 246 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 247 return nil, errors.Annotate(err, "listing cluster members") 248 } 249 aZones := make([]common.AvailabilityZone, len(nodes)) 250 for i, n := range nodes { 251 aZones[i] = &lxdAvailabilityZone{n} 252 } 253 return aZones, nil 254 } 255 256 // InstanceAvailabilityZoneNames (ZonedEnviron) returns the names of the 257 // availability zones for the specified instances. 258 // For containers, this means the LXD server node names where they reside. 259 func (env *environ) InstanceAvailabilityZoneNames( 260 ctx context.ProviderCallContext, ids []instance.Id, 261 ) ([]string, error) { 262 instances, err := env.Instances(ctx, ids) 263 if err != nil && err != environs.ErrPartialInstances { 264 return nil, err 265 } 266 267 // If not clustered, just report all input IDs as being in the zone 268 // represented by the single server. 269 if !env.server.IsClustered() { 270 zones := make([]string, len(ids)) 271 n := env.server.Name() 272 for i := range zones { 273 zones[i] = n 274 } 275 return zones, nil 276 } 277 278 zones := make([]string, len(instances)) 279 for i, ins := range instances { 280 if ei, ok := ins.(*environInstance); ok { 281 zones[i] = ei.container.Location 282 } 283 } 284 return zones, nil 285 } 286 287 // DeriveAvailabilityZones (ZonedEnviron) attempts to derive availability zones 288 // from the specified StartInstanceParams. 289 func (env *environ) DeriveAvailabilityZones( 290 ctx context.ProviderCallContext, args environs.StartInstanceParams, 291 ) ([]string, error) { 292 p, err := env.parsePlacement(ctx, args.Placement) 293 if err != nil { 294 return nil, errors.Trace(err) 295 } 296 if p.nodeName == "" { 297 return nil, nil 298 } 299 return []string{p.nodeName}, nil 300 } 301 302 // MaybeWriteLXDProfile implements environs.LXDProfiler. 303 func (env *environ) MaybeWriteLXDProfile(pName string, put *charm.LXDProfile) error { 304 hasProfile, err := env.server.HasProfile(pName) 305 if err != nil { 306 return errors.Trace(err) 307 } 308 if hasProfile { 309 logger.Debugf("lxd profile %q already exists, not written again", pName) 310 return nil 311 } 312 post := api.ProfilesPost{ 313 Name: pName, 314 ProfilePut: api.ProfilePut(*put), 315 } 316 if err = env.server.CreateProfile(post); err != nil { 317 return errors.Trace(err) 318 } 319 logger.Debugf("wrote lxd profile %q", pName) 320 return nil 321 } 322 323 // LXDProfileNames implements environs.LXDProfiler. 324 func (env *environ) LXDProfileNames(containerName string) ([]string, error) { 325 return env.server.GetContainerProfiles(containerName) 326 } 327 328 // ReplaceLXDProfile implements environs.LXDProfiler. 329 func (env *environ) ReplaceOrAddInstanceProfile(instId, oldProfile, newProfile string, put *charm.LXDProfile) ([]string, error) { 330 if put != nil { 331 if err := env.MaybeWriteLXDProfile(newProfile, put); err != nil { 332 return []string{}, errors.Trace(err) 333 } 334 } 335 if err := env.server.ReplaceOrAddContainerProfile(instId, oldProfile, newProfile); err != nil { 336 return []string{}, errors.Trace(err) 337 } 338 if oldProfile != "" { 339 if err := env.server.DeleteProfile(oldProfile); err != nil { 340 // most likely the failure is because the profile is already in use 341 logger.Debugf("failed to delete profile %q: %s", oldProfile, err) 342 } 343 } 344 return env.LXDProfileNames(instId) 345 }