github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/container/lxd/lxd.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  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  
    14  	"github.com/juju/juju/cloudconfig/containerinit"
    15  	"github.com/juju/juju/cloudconfig/instancecfg"
    16  	"github.com/juju/juju/constraints"
    17  	"github.com/juju/juju/container"
    18  	"github.com/juju/juju/instance"
    19  	"github.com/juju/juju/network"
    20  	"github.com/juju/juju/status"
    21  	"github.com/juju/juju/tools/lxdclient"
    22  )
    23  
    24  var (
    25  	logger = loggo.GetLogger("juju.container.lxd")
    26  )
    27  
    28  const lxdDefaultProfileName = "default"
    29  
    30  // XXX: should we allow managing containers on other hosts? this is
    31  // functionality LXD gives us and from discussion juju would use eventually for
    32  // the local provider, so the APIs probably need to be changed to pass extra
    33  // args around. I'm punting for now.
    34  type containerManager struct {
    35  	modelUUID string
    36  	namespace instance.Namespace
    37  	// A cached client.
    38  	client *lxdclient.Client
    39  }
    40  
    41  // containerManager implements container.Manager.
    42  var _ container.Manager = (*containerManager)(nil)
    43  
    44  func ConnectLocal() (*lxdclient.Client, error) {
    45  	cfg := lxdclient.Config{
    46  		Remote: lxdclient.Local,
    47  	}
    48  
    49  	cfg, err := cfg.WithDefaults()
    50  	if err != nil {
    51  		return nil, errors.Trace(err)
    52  	}
    53  
    54  	client, err := lxdclient.Connect(cfg)
    55  	if err != nil {
    56  		return nil, errors.Trace(err)
    57  	}
    58  
    59  	return client, nil
    60  }
    61  
    62  // NewContainerManager creates the entity that knows how to create and manage
    63  // LXD containers.
    64  // TODO(jam): This needs to grow support for things like LXC's ImageURLGetter
    65  // functionality.
    66  func NewContainerManager(conf container.ManagerConfig) (container.Manager, error) {
    67  	modelUUID := conf.PopValue(container.ConfigModelUUID)
    68  	if modelUUID == "" {
    69  		return nil, errors.Errorf("model UUID is required")
    70  	}
    71  	namespace, err := instance.NewNamespace(modelUUID)
    72  	if err != nil {
    73  		return nil, errors.Trace(err)
    74  	}
    75  
    76  	conf.WarnAboutUnused()
    77  	return &containerManager{
    78  		modelUUID: modelUUID,
    79  		namespace: namespace,
    80  	}, nil
    81  }
    82  
    83  // Namespace implements container.Manager.
    84  func (manager *containerManager) Namespace() instance.Namespace {
    85  	return manager.namespace
    86  }
    87  
    88  func (manager *containerManager) CreateContainer(
    89  	instanceConfig *instancecfg.InstanceConfig,
    90  	cons constraints.Value,
    91  	series string,
    92  	networkConfig *container.NetworkConfig,
    93  	storageConfig *container.StorageConfig,
    94  	callback container.StatusCallback,
    95  ) (inst instance.Instance, _ *instance.HardwareCharacteristics, err error) {
    96  
    97  	defer func() {
    98  		if err != nil {
    99  			callback(status.ProvisioningError, fmt.Sprintf("Creating container: %v", err), nil)
   100  		}
   101  	}()
   102  
   103  	if manager.client == nil {
   104  		manager.client, err = ConnectLocal()
   105  		if err != nil {
   106  			err = errors.Annotatef(err, "failed to connect to local LXD")
   107  			return
   108  		}
   109  	}
   110  
   111  	err = manager.client.EnsureImageExists(series,
   112  		lxdclient.DefaultImageSources,
   113  		func(progress string) {
   114  			callback(status.Provisioning, progress, nil)
   115  		})
   116  	if err != nil {
   117  		err = errors.Annotatef(err, "failed to ensure LXD image")
   118  		return
   119  	}
   120  
   121  	name, err := manager.namespace.Hostname(instanceConfig.MachineId)
   122  	if err != nil {
   123  		return nil, nil, errors.Trace(err)
   124  	}
   125  
   126  	// Do not pass networkConfig, as we want to directly inject our own ENI
   127  	// rather than using cloud-init.
   128  	userData, err := containerinit.CloudInitUserData(instanceConfig, nil)
   129  	if err != nil {
   130  		return
   131  	}
   132  
   133  	metadata := map[string]string{
   134  		lxdclient.UserdataKey: string(userData),
   135  		// An extra piece of info to let people figure out where this
   136  		// thing came from.
   137  		"user.juju-model": manager.modelUUID,
   138  
   139  		// Make sure these come back up on host reboot.
   140  		"boot.autostart": "true",
   141  	}
   142  
   143  	nics, err := networkDevices(networkConfig)
   144  	if err != nil {
   145  		return
   146  	}
   147  
   148  	// TODO(macgreagoir) This might be dead code. Do we always get
   149  	// len(nics) > 0?
   150  	profiles := []string{}
   151  
   152  	if len(nics) == 0 {
   153  		logger.Infof("instance %q configured with %q profile", name, lxdDefaultProfileName)
   154  		profiles = append(profiles, lxdDefaultProfileName)
   155  	} else {
   156  		logger.Infof("instance %q configured with %v network devices", name, nics)
   157  	}
   158  
   159  	// Push the required /etc/network/interfaces file to the container.
   160  	// By pushing this file (which happens after LXD init, and before LXD
   161  	// start) we ensure that we get Juju's version of ENI, as opposed to
   162  	// the default LXD version, which may assume it can do DHCP over eth0.
   163  	// Especially on a multi-nic host, it is possible for MAAS to provide
   164  	// DHCP on a different space to that which the container eth0 interface
   165  	// will be bridged, or not provide DHCP at all.
   166  	eni, err := containerinit.GenerateNetworkConfig(networkConfig)
   167  	if err != nil {
   168  		err = errors.Annotatef(err, "failed to generate /etc/network/interfaces content")
   169  		return
   170  	}
   171  
   172  	spec := lxdclient.InstanceSpec{
   173  		Name:     name,
   174  		Image:    manager.client.ImageNameForSeries(series),
   175  		Metadata: metadata,
   176  		Devices:  nics,
   177  		Profiles: profiles,
   178  		Files: lxdclient.Files{
   179  			lxdclient.File{
   180  				Content: []byte(eni),
   181  				Path:    "/etc/network/interfaces",
   182  				GID:     0,
   183  				UID:     0,
   184  				Mode:    0644,
   185  			},
   186  		},
   187  	}
   188  
   189  	logger.Infof("starting instance %q (image %q)...", spec.Name, spec.Image)
   190  	callback(status.Provisioning, "Starting container", nil)
   191  	_, err = manager.client.AddInstance(spec)
   192  	if err != nil {
   193  		return
   194  	}
   195  
   196  	callback(status.Running, "Container started", nil)
   197  	inst = &lxdInstance{name, manager.client}
   198  	return
   199  }
   200  
   201  func (manager *containerManager) DestroyContainer(id instance.Id) error {
   202  	if manager.client == nil {
   203  		var err error
   204  		manager.client, err = ConnectLocal()
   205  		if err != nil {
   206  			return err
   207  		}
   208  	}
   209  	return errors.Trace(manager.client.RemoveInstances(manager.namespace.Prefix(), string(id)))
   210  }
   211  
   212  func (manager *containerManager) ListContainers() (result []instance.Instance, err error) {
   213  	result = []instance.Instance{}
   214  	if manager.client == nil {
   215  		manager.client, err = ConnectLocal()
   216  		if err != nil {
   217  			return
   218  		}
   219  	}
   220  
   221  	lxdInstances, err := manager.client.Instances(manager.namespace.Prefix())
   222  	if err != nil {
   223  		return
   224  	}
   225  
   226  	for _, i := range lxdInstances {
   227  		result = append(result, &lxdInstance{i.Name, manager.client})
   228  	}
   229  
   230  	return
   231  }
   232  
   233  func (manager *containerManager) IsInitialized() bool {
   234  	if manager.client != nil {
   235  		return true
   236  	}
   237  
   238  	// NewClient does a roundtrip to the server to make sure it understands
   239  	// the versions, so all we need to do is connect above and we're done.
   240  	var err error
   241  	manager.client, err = ConnectLocal()
   242  	return err == nil
   243  }
   244  
   245  // HasLXDSupport returns false when this juju binary was not built with LXD
   246  // support (i.e. it was built on a golang version < 1.2
   247  func HasLXDSupport() bool {
   248  	return true
   249  }
   250  
   251  func nicDevice(deviceName, parentDevice, hwAddr string, mtu int) (lxdclient.Device, error) {
   252  	device := make(lxdclient.Device)
   253  
   254  	device["type"] = "nic"
   255  	device["nictype"] = "bridged"
   256  
   257  	if deviceName == "" {
   258  		return nil, errors.Errorf("invalid device name")
   259  	}
   260  	device["name"] = deviceName
   261  
   262  	if parentDevice == "" {
   263  		return nil, errors.Errorf("invalid parent device name")
   264  	}
   265  	device["parent"] = parentDevice
   266  
   267  	if hwAddr != "" {
   268  		device["hwaddr"] = hwAddr
   269  	}
   270  
   271  	if mtu > 0 {
   272  		device["mtu"] = fmt.Sprintf("%v", mtu)
   273  	}
   274  
   275  	return device, nil
   276  }
   277  
   278  func networkDevices(networkConfig *container.NetworkConfig) (lxdclient.Devices, error) {
   279  	nics := make(lxdclient.Devices)
   280  
   281  	if len(networkConfig.Interfaces) > 0 {
   282  		for _, v := range networkConfig.Interfaces {
   283  			if v.InterfaceType == network.LoopbackInterface {
   284  				continue
   285  			}
   286  			if v.InterfaceType != network.EthernetInterface {
   287  				return nil, errors.Errorf("interface type %q not supported", v.InterfaceType)
   288  			}
   289  			parentDevice := v.ParentInterfaceName
   290  			device, err := nicDevice(v.InterfaceName, parentDevice, v.MACAddress, v.MTU)
   291  			if err != nil {
   292  				return nil, errors.Trace(err)
   293  			}
   294  			nics[v.InterfaceName] = device
   295  		}
   296  	} else if networkConfig.Device != "" {
   297  		device, err := nicDevice("eth0", networkConfig.Device, "", networkConfig.MTU)
   298  		if err != nil {
   299  			return nil, errors.Trace(err)
   300  		}
   301  		nics["eth0"] = device
   302  	}
   303  
   304  	return nics, nil
   305  }