github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/provider/lxd/environ_broker.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // +build go1.3
     5  
     6  package lxd
     7  
     8  import (
     9  	"fmt"
    10  	"strings"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/utils/arch"
    14  
    15  	"github.com/juju/juju/agent"
    16  	"github.com/juju/juju/cloudconfig/instancecfg"
    17  	"github.com/juju/juju/cloudconfig/providerinit"
    18  	"github.com/juju/juju/environs"
    19  	"github.com/juju/juju/environs/tags"
    20  	"github.com/juju/juju/instance"
    21  	"github.com/juju/juju/provider/common"
    22  	"github.com/juju/juju/state/multiwatcher"
    23  	"github.com/juju/juju/status"
    24  	"github.com/juju/juju/tools"
    25  	"github.com/juju/juju/tools/lxdclient"
    26  )
    27  
    28  func isController(icfg *instancecfg.InstanceConfig) bool {
    29  	return multiwatcher.AnyJobNeedsState(icfg.Jobs...)
    30  }
    31  
    32  // MaintainInstance is specified in the InstanceBroker interface.
    33  func (*environ) MaintainInstance(args environs.StartInstanceParams) error {
    34  	return nil
    35  }
    36  
    37  // StartInstance implements environs.InstanceBroker.
    38  func (env *environ) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) {
    39  	// Start a new instance.
    40  
    41  	series := args.Tools.OneSeries()
    42  	logger.Debugf("StartInstance: %q, %s", args.InstanceConfig.MachineId, series)
    43  
    44  	if err := env.finishInstanceConfig(args); err != nil {
    45  		return nil, errors.Trace(err)
    46  	}
    47  
    48  	// TODO(ericsnow) Handle constraints?
    49  
    50  	raw, err := env.newRawInstance(args)
    51  	if err != nil {
    52  		if args.StatusCallback != nil {
    53  			args.StatusCallback(status.StatusProvisioningError, err.Error(), nil)
    54  		}
    55  		return nil, errors.Trace(err)
    56  	}
    57  	logger.Infof("started instance %q", raw.Name)
    58  	inst := newInstance(raw, env)
    59  
    60  	// Build the result.
    61  	hwc := env.getHardwareCharacteristics(args, inst)
    62  	result := environs.StartInstanceResult{
    63  		Instance: inst,
    64  		Hardware: hwc,
    65  	}
    66  	return &result, nil
    67  }
    68  
    69  func (env *environ) finishInstanceConfig(args environs.StartInstanceParams) error {
    70  	// TODO(natefinch): This is only correct so long as the lxd is running on
    71  	// the local machine.  If/when we support a remote lxd environment, we'll
    72  	// need to change this to match the arch of the remote machine.
    73  	tools, err := args.Tools.Match(tools.Filter{Arch: arch.HostArch()})
    74  	if err != nil {
    75  		return errors.Trace(err)
    76  	}
    77  	if len(tools) == 0 {
    78  		return errors.Errorf("No tools available for architecture %q", arch.HostArch())
    79  	}
    80  	if err := args.InstanceConfig.SetTools(tools); err != nil {
    81  		return errors.Trace(err)
    82  	}
    83  	logger.Debugf("tools: %#v", args.InstanceConfig.ToolsList())
    84  
    85  	if err := instancecfg.FinishInstanceConfig(args.InstanceConfig, env.ecfg.Config); err != nil {
    86  		return errors.Trace(err)
    87  	}
    88  
    89  	// TODO: evaluate the impact of setting the constraints on the
    90  	// instanceConfig for all machines rather than just controller nodes.
    91  	// This limitation is why the constraints are assigned directly here.
    92  	args.InstanceConfig.Constraints = args.Constraints
    93  
    94  	args.InstanceConfig.AgentEnvironment[agent.Namespace] = env.ecfg.namespace()
    95  
    96  	return nil
    97  }
    98  
    99  func (env *environ) getImageSources() ([]lxdclient.Remote, error) {
   100  	metadataSources, err := environs.ImageMetadataSources(env)
   101  	if err != nil {
   102  		return nil, errors.Trace(err)
   103  	}
   104  	remotes := make([]lxdclient.Remote, 0)
   105  	for _, source := range metadataSources {
   106  		url, err := source.URL("")
   107  		if err != nil {
   108  			logger.Debugf("failed to get the URL for metadataSource: %s", err)
   109  			continue
   110  		}
   111  		// NOTE(jam) LXD only allows you to pass HTTPS URLs. So strip
   112  		// off http:// and replace it with https://
   113  		// Arguably we could give the user a direct error if
   114  		// env.ImageMetadataURL is http instead of https, but we also
   115  		// get http from the DefaultImageSources, which is why we
   116  		// replace it.
   117  		// TODO(jam) Maybe we could add a Validate step that ensures
   118  		// image-metadata-url is an "https://" URL, so that Users get a
   119  		// "your configuration is wrong" error, rather than silently
   120  		// changing it and having them get confused.
   121  		// https://github.com/lxc/lxd/issues/1763
   122  		if strings.HasPrefix(url, "http://") {
   123  			url = strings.TrimPrefix(url, "http://")
   124  			url = "https://" + url
   125  			logger.Debugf("LXD requires https://, using: %s", url)
   126  		}
   127  		remotes = append(remotes, lxdclient.Remote{
   128  			Name:          source.Description(),
   129  			Host:          url,
   130  			Protocol:      lxdclient.SimplestreamsProtocol,
   131  			Cert:          nil,
   132  			ServerPEMCert: "",
   133  		})
   134  	}
   135  	return remotes, nil
   136  }
   137  
   138  // newRawInstance is where the new physical instance is actually
   139  // provisioned, relative to the provided args and spec. Info for that
   140  // low-level instance is returned.
   141  func (env *environ) newRawInstance(args environs.StartInstanceParams) (*lxdclient.Instance, error) {
   142  	machineID := common.MachineFullName(env.Config().UUID(), args.InstanceConfig.MachineId)
   143  
   144  	// Note: other providers have the ImageMetadata already read for them
   145  	// and passed in as args.ImageMetadata. However, lxd provider doesn't
   146  	// use datatype: image-ids, it uses datatype: image-download, and we
   147  	// don't have a registered cloud/region.
   148  	imageSources, err := env.getImageSources()
   149  	if err != nil {
   150  		return nil, errors.Trace(err)
   151  	}
   152  
   153  	series := args.Tools.OneSeries()
   154  	// TODO(jam): We should get this information from EnsureImageExists, or
   155  	// something given to us from 'raw', not assume it ourselves.
   156  	image := "ubuntu-" + series
   157  	// TODO: support args.Constraints.Arch, we'll want to map from
   158  
   159  	var callback func(string)
   160  	if args.StatusCallback != nil {
   161  		callback = func(copyProgress string) {
   162  			args.StatusCallback(status.StatusAllocating, copyProgress, nil)
   163  		}
   164  	}
   165  	if err := env.raw.EnsureImageExists(series, imageSources, callback); err != nil {
   166  		return nil, errors.Trace(err)
   167  	}
   168  
   169  	metadata, err := getMetadata(args)
   170  	if err != nil {
   171  		return nil, errors.Trace(err)
   172  	}
   173  	//tags := []string{
   174  	//	env.globalFirewallName(),
   175  	//	machineID,
   176  	//}
   177  	// TODO(ericsnow) Use the env ID for the network name (instead of default)?
   178  	// TODO(ericsnow) Make the network name configurable?
   179  	// TODO(ericsnow) Support multiple networks?
   180  	// TODO(ericsnow) Use a different net interface name? Configurable?
   181  	instSpec := lxdclient.InstanceSpec{
   182  		Name:  machineID,
   183  		Image: image,
   184  		//Type:              spec.InstanceType.Name,
   185  		//Disks:             getDisks(spec, args.Constraints),
   186  		//NetworkInterfaces: []string{"ExternalNAT"},
   187  		Metadata: metadata,
   188  		Profiles: []string{
   189  			//TODO(wwitzel3) allow the user to specify lxc profiles to apply. This allows the
   190  			// user to setup any custom devices order config settings for their environment.
   191  			// Also we must ensure that a device with the parent: lxcbr0 exists in at least
   192  			// one of the profiles.
   193  			"default",
   194  			env.profileName(),
   195  		},
   196  		//Tags:              tags,
   197  		// Network is omitted (left empty).
   198  	}
   199  
   200  	logger.Infof("starting instance %q (image %q)...", instSpec.Name, instSpec.Image)
   201  	if args.StatusCallback != nil {
   202  		args.StatusCallback(status.StatusAllocating, "starting instance", nil)
   203  	}
   204  	inst, err := env.raw.AddInstance(instSpec)
   205  	if err != nil {
   206  		return nil, errors.Trace(err)
   207  	}
   208  	if args.StatusCallback != nil {
   209  		args.StatusCallback(status.StatusRunning, "Container started", nil)
   210  	}
   211  	return inst, nil
   212  }
   213  
   214  // getMetadata builds the raw "user-defined" metadata for the new
   215  // instance (relative to the provided args) and returns it.
   216  func getMetadata(args environs.StartInstanceParams) (map[string]string, error) {
   217  	renderer := lxdRenderer{}
   218  	uncompressed, err := providerinit.ComposeUserData(args.InstanceConfig, nil, renderer)
   219  	if err != nil {
   220  		return nil, errors.Annotate(err, "cannot make user data")
   221  	}
   222  	logger.Debugf("LXD user data; %d bytes", len(uncompressed))
   223  
   224  	// TODO(ericsnow) Looks like LXD does not handle gzipped userdata
   225  	// correctly.  It likely has to do with the HTTP transport, much
   226  	// as we have to b64encode the userdata for GCE.  Until that is
   227  	// resolved we simply pass the plain text.
   228  	//compressed := utils.Gzip(compressed)
   229  	userdata := string(uncompressed)
   230  
   231  	metadata := map[string]string{
   232  		// store the cloud-config userdata for cloud-init.
   233  		metadataKeyCloudInit: userdata,
   234  	}
   235  	for k, v := range args.InstanceConfig.Tags {
   236  		if !strings.HasPrefix(k, tags.JujuTagPrefix) {
   237  			// Since some metadata is interpreted by LXD,
   238  			// we cannot allow arbitrary tags to be passed
   239  			// in by the user. We currently only pass through
   240  			// Juju-defined tags.
   241  			//
   242  			// TODO(axw) 2016-04-11 #1568666
   243  			// We should reject non-juju tags in config validation.
   244  			logger.Debugf("ignoring non-juju tag: %s=%s", k, v)
   245  			continue
   246  		}
   247  		metadata[k] = v
   248  	}
   249  
   250  	return metadata, nil
   251  }
   252  
   253  // getHardwareCharacteristics compiles hardware-related details about
   254  // the given instance and relative to the provided spec and returns it.
   255  func (env *environ) getHardwareCharacteristics(args environs.StartInstanceParams, inst *environInstance) *instance.HardwareCharacteristics {
   256  	raw := inst.raw.Hardware
   257  
   258  	archStr := raw.Architecture
   259  	if archStr == "unknown" || !arch.IsSupportedArch(archStr) {
   260  		// TODO(ericsnow) This special-case should be improved.
   261  		archStr = arch.HostArch()
   262  	}
   263  
   264  	hwc, err := instance.ParseHardware(
   265  		"arch="+archStr,
   266  		fmt.Sprintf("cpu-cores=%d", raw.NumCores),
   267  		fmt.Sprintf("mem=%dM", raw.MemoryMB),
   268  		//"root-disk=",
   269  		//"tags=",
   270  	)
   271  	if err != nil {
   272  		logger.Errorf("unexpected problem parsing hardware info: %v", err)
   273  		// Keep moving...
   274  	}
   275  	return &hwc
   276  }
   277  
   278  // AllInstances implements environs.InstanceBroker.
   279  func (env *environ) AllInstances() ([]instance.Instance, error) {
   280  	environInstances, err := env.allInstances()
   281  	instances := make([]instance.Instance, len(environInstances))
   282  	for i, inst := range environInstances {
   283  		if inst == nil {
   284  			continue
   285  		}
   286  		instances[i] = inst
   287  	}
   288  	return instances, err
   289  }
   290  
   291  // StopInstances implements environs.InstanceBroker.
   292  func (env *environ) StopInstances(instances ...instance.Id) error {
   293  	var ids []string
   294  	for _, id := range instances {
   295  		ids = append(ids, string(id))
   296  	}
   297  
   298  	prefix := common.MachineFullName(env.Config().UUID(), "")
   299  	err := env.raw.RemoveInstances(prefix, ids...)
   300  	return errors.Trace(err)
   301  }