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  }