1
// Copyright 2015 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
11
"github.com/juju/errors"
12
"github.com/juju/loggo"
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"
25
logger = loggo.GetLogger("juju.container.lxd")
28
const lxdDefaultProfileName = "default"
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 {
36
namespace instance.Namespace
38
client *lxdclient.Client
41
// containerManager implements container.Manager.
42
var _ container.Manager = (*containerManager)(nil)
44
func ConnectLocal() (*lxdclient.Client, error) {
45
cfg := lxdclient.Config{
46
Remote: lxdclient.Local,
49
cfg, err := cfg.WithDefaults()
51
return nil, errors.Trace(err)
54
client, err := lxdclient.Connect(cfg)
56
return nil, errors.Trace(err)
62
// NewContainerManager creates the entity that knows how to create and manage
64
// TODO(jam): This needs to grow support for things like LXC's ImageURLGetter
66
func NewContainerManager(conf container.ManagerConfig) (container.Manager, error) {
67
modelUUID := conf.PopValue(container.ConfigModelUUID)
69
return nil, errors.Errorf("model UUID is required")
71
namespace, err := instance.NewNamespace(modelUUID)
73
return nil, errors.Trace(err)
76
conf.WarnAboutUnused()
77
return &containerManager{
83
// Namespace implements container.Manager.
84
func (manager *containerManager) Namespace() instance.Namespace {
85
return manager.namespace
88
func (manager *containerManager) CreateContainer(
89
instanceConfig *instancecfg.InstanceConfig,
90
cons constraints.Value,
92
networkConfig *container.NetworkConfig,
93
storageConfig *container.StorageConfig,
94
callback container.StatusCallback,
95
) (inst instance.Instance, _ *instance.HardwareCharacteristics, err error) {
99
callback(status.StatusProvisioningError, fmt.Sprintf("Creating container: %v", err), nil)
103
if manager.client == nil {
104
manager.client, err = ConnectLocal()
106
err = errors.Annotatef(err, "failed to connect to local LXD")
111
err = manager.client.EnsureImageExists(series,
112
lxdclient.DefaultImageSources,
113
func(progress string) {
114
callback(status.StatusProvisioning, progress, nil)
117
err = errors.Annotatef(err, "failed to ensure LXD image")
121
name, err := manager.namespace.Hostname(instanceConfig.MachineId)
123
return nil, nil, errors.Trace(err)
126
userData, err := containerinit.CloudInitUserData(instanceConfig, networkConfig)
131
metadata := map[string]string{
132
lxdclient.UserdataKey: string(userData),
133
// An extra piece of info to let people figure out where this
135
"user.juju-model": manager.modelUUID,
137
// Make sure these come back up on host reboot.
138
"boot.autostart": "true",
141
nics, err := networkDevices(networkConfig)
146
profiles := []string{}
149
logger.Infof("instance %q configured with %q profile", name, lxdDefaultProfileName)
150
profiles = append(profiles, lxdDefaultProfileName)
152
logger.Infof("instance %q configured with %v network devices", name, nics)
155
spec := lxdclient.InstanceSpec{
157
Image: manager.client.ImageNameForSeries(series),
163
logger.Infof("starting instance %q (image %q)...", spec.Name, spec.Image)
164
callback(status.StatusProvisioning, "Starting container", nil)
165
_, err = manager.client.AddInstance(spec)
170
callback(status.StatusRunning, "Container started", nil)
171
inst = &lxdInstance{name, manager.client}
175
func (manager *containerManager) DestroyContainer(id instance.Id) error {
176
if manager.client == nil {
178
manager.client, err = ConnectLocal()
183
return errors.Trace(manager.client.RemoveInstances(manager.namespace.Prefix(), string(id)))
186
func (manager *containerManager) ListContainers() (result []instance.Instance, err error) {
187
result = []instance.Instance{}
188
if manager.client == nil {
189
manager.client, err = ConnectLocal()
195
lxdInstances, err := manager.client.Instances(manager.namespace.Prefix())
200
for _, i := range lxdInstances {
201
result = append(result, &lxdInstance{i.Name, manager.client})
207
func (manager *containerManager) IsInitialized() bool {
208
if manager.client != nil {
212
// NewClient does a roundtrip to the server to make sure it understands
213
// the versions, so all we need to do is connect above and we're done.
215
manager.client, err = ConnectLocal()
219
// HasLXDSupport returns false when this juju binary was not built with LXD
220
// support (i.e. it was built on a golang version < 1.2
221
func HasLXDSupport() bool {
225
func nicDevice(deviceName, parentDevice, hwAddr string, mtu int) (lxdclient.Device, error) {
226
device := make(lxdclient.Device)
228
device["type"] = "nic"
229
device["nictype"] = "bridged"
231
if deviceName == "" {
232
return nil, errors.Errorf("invalid device name")
234
device["name"] = deviceName
236
if parentDevice == "" {
237
return nil, errors.Errorf("invalid parent device name")
239
device["parent"] = parentDevice
242
device["hwaddr"] = hwAddr
246
device["mtu"] = fmt.Sprintf("%v", mtu)
252
func networkDevices(networkConfig *container.NetworkConfig) (lxdclient.Devices, error) {
253
nics := make(lxdclient.Devices)
255
if len(networkConfig.Interfaces) > 0 {
256
for _, v := range networkConfig.Interfaces {
257
if v.InterfaceType == network.LoopbackInterface {
260
if v.InterfaceType != network.EthernetInterface {
261
return nil, errors.Errorf("interface type %q not supported", v.InterfaceType)
263
parentDevice := v.ParentInterfaceName
264
device, err := nicDevice(v.InterfaceName, parentDevice, v.MACAddress, v.MTU)
266
return nil, errors.Trace(err)
268
nics[v.InterfaceName] = device
270
} else if networkConfig.Device != "" {
271
device, err := nicDevice("eth0", networkConfig.Device, "", networkConfig.MTU)
273
return nil, errors.Trace(err)
275
nics["eth0"] = device