~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/container/lxd/lxd.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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.StatusProvisioningError, 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.StatusProvisioning, 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
        userData, err := containerinit.CloudInitUserData(instanceConfig, networkConfig)
 
127
        if err != nil {
 
128
                return
 
129
        }
 
130
 
 
131
        metadata := map[string]string{
 
132
                lxdclient.UserdataKey: string(userData),
 
133
                // An extra piece of info to let people figure out where this
 
134
                // thing came from.
 
135
                "user.juju-model": manager.modelUUID,
 
136
 
 
137
                // Make sure these come back up on host reboot.
 
138
                "boot.autostart": "true",
 
139
        }
 
140
 
 
141
        nics, err := networkDevices(networkConfig)
 
142
        if err != nil {
 
143
                return
 
144
        }
 
145
 
 
146
        profiles := []string{}
 
147
 
 
148
        if len(nics) == 0 {
 
149
                logger.Infof("instance %q configured with %q profile", name, lxdDefaultProfileName)
 
150
                profiles = append(profiles, lxdDefaultProfileName)
 
151
        } else {
 
152
                logger.Infof("instance %q configured with %v network devices", name, nics)
 
153
        }
 
154
 
 
155
        spec := lxdclient.InstanceSpec{
 
156
                Name:     name,
 
157
                Image:    manager.client.ImageNameForSeries(series),
 
158
                Metadata: metadata,
 
159
                Devices:  nics,
 
160
                Profiles: profiles,
 
161
        }
 
162
 
 
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)
 
166
        if err != nil {
 
167
                return
 
168
        }
 
169
 
 
170
        callback(status.StatusRunning, "Container started", nil)
 
171
        inst = &lxdInstance{name, manager.client}
 
172
        return
 
173
}
 
174
 
 
175
func (manager *containerManager) DestroyContainer(id instance.Id) error {
 
176
        if manager.client == nil {
 
177
                var err error
 
178
                manager.client, err = ConnectLocal()
 
179
                if err != nil {
 
180
                        return err
 
181
                }
 
182
        }
 
183
        return errors.Trace(manager.client.RemoveInstances(manager.namespace.Prefix(), string(id)))
 
184
}
 
185
 
 
186
func (manager *containerManager) ListContainers() (result []instance.Instance, err error) {
 
187
        result = []instance.Instance{}
 
188
        if manager.client == nil {
 
189
                manager.client, err = ConnectLocal()
 
190
                if err != nil {
 
191
                        return
 
192
                }
 
193
        }
 
194
 
 
195
        lxdInstances, err := manager.client.Instances(manager.namespace.Prefix())
 
196
        if err != nil {
 
197
                return
 
198
        }
 
199
 
 
200
        for _, i := range lxdInstances {
 
201
                result = append(result, &lxdInstance{i.Name, manager.client})
 
202
        }
 
203
 
 
204
        return
 
205
}
 
206
 
 
207
func (manager *containerManager) IsInitialized() bool {
 
208
        if manager.client != nil {
 
209
                return true
 
210
        }
 
211
 
 
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.
 
214
        var err error
 
215
        manager.client, err = ConnectLocal()
 
216
        return err == nil
 
217
}
 
218
 
 
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 {
 
222
        return true
 
223
}
 
224
 
 
225
func nicDevice(deviceName, parentDevice, hwAddr string, mtu int) (lxdclient.Device, error) {
 
226
        device := make(lxdclient.Device)
 
227
 
 
228
        device["type"] = "nic"
 
229
        device["nictype"] = "bridged"
 
230
 
 
231
        if deviceName == "" {
 
232
                return nil, errors.Errorf("invalid device name")
 
233
        }
 
234
        device["name"] = deviceName
 
235
 
 
236
        if parentDevice == "" {
 
237
                return nil, errors.Errorf("invalid parent device name")
 
238
        }
 
239
        device["parent"] = parentDevice
 
240
 
 
241
        if hwAddr != "" {
 
242
                device["hwaddr"] = hwAddr
 
243
        }
 
244
 
 
245
        if mtu > 0 {
 
246
                device["mtu"] = fmt.Sprintf("%v", mtu)
 
247
        }
 
248
 
 
249
        return device, nil
 
250
}
 
251
 
 
252
func networkDevices(networkConfig *container.NetworkConfig) (lxdclient.Devices, error) {
 
253
        nics := make(lxdclient.Devices)
 
254
 
 
255
        if len(networkConfig.Interfaces) > 0 {
 
256
                for _, v := range networkConfig.Interfaces {
 
257
                        if v.InterfaceType == network.LoopbackInterface {
 
258
                                continue
 
259
                        }
 
260
                        if v.InterfaceType != network.EthernetInterface {
 
261
                                return nil, errors.Errorf("interface type %q not supported", v.InterfaceType)
 
262
                        }
 
263
                        parentDevice := v.ParentInterfaceName
 
264
                        device, err := nicDevice(v.InterfaceName, parentDevice, v.MACAddress, v.MTU)
 
265
                        if err != nil {
 
266
                                return nil, errors.Trace(err)
 
267
                        }
 
268
                        nics[v.InterfaceName] = device
 
269
                }
 
270
        } else if networkConfig.Device != "" {
 
271
                device, err := nicDevice("eth0", networkConfig.Device, "", networkConfig.MTU)
 
272
                if err != nil {
 
273
                        return nil, errors.Trace(err)
 
274
                }
 
275
                nics["eth0"] = device
 
276
        }
 
277
 
 
278
        return nics, nil
 
279
}