~ubuntu-branches/ubuntu/vivid/juju-core/vivid-updates

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/worker/provisioner/lxc-broker.go

  • Committer: Package Import Robot
  • Author(s): Curtis C. Hovey
  • Date: 2015-09-29 19:43:29 UTC
  • mfrom: (47.1.4 wily-proposed)
  • Revision ID: package-import@ubuntu.com-20150929194329-9y496tbic30hc7vp
Tags: 1.24.6-0ubuntu1~15.04.1
Backport of 1.24.6 from wily. (LP: #1500916, #1497087)

Show diffs side-by-side

added added

removed removed

Lines of Context:
4
4
package provisioner
5
5
 
6
6
import (
 
7
        "bufio"
 
8
        "bytes"
 
9
        "fmt"
 
10
        "net"
 
11
        "os"
 
12
        "strings"
 
13
        "text/template"
 
14
 
7
15
        "github.com/juju/errors"
8
16
        "github.com/juju/loggo"
 
17
        "github.com/juju/names"
 
18
        "github.com/juju/utils/exec"
9
19
 
10
20
        "github.com/juju/juju/agent"
 
21
        apiprovisioner "github.com/juju/juju/api/provisioner"
11
22
        "github.com/juju/juju/apiserver/params"
 
23
        "github.com/juju/juju/cloudconfig/instancecfg"
12
24
        "github.com/juju/juju/container"
13
25
        "github.com/juju/juju/container/lxc"
14
26
        "github.com/juju/juju/environs"
15
27
        "github.com/juju/juju/instance"
 
28
        "github.com/juju/juju/network"
16
29
        "github.com/juju/juju/tools"
17
30
        "github.com/juju/juju/version"
18
31
)
23
36
 
24
37
type APICalls interface {
25
38
        ContainerConfig() (params.ContainerConfig, error)
 
39
        PrepareContainerInterfaceInfo(names.MachineTag) ([]network.InterfaceInfo, error)
 
40
        GetContainerInterfaceInfo(names.MachineTag) ([]network.InterfaceInfo, error)
26
41
}
27
42
 
 
43
var _ APICalls = (*apiprovisioner.State)(nil)
 
44
 
28
45
// Override for testing.
29
46
var NewLxcBroker = newLxcBroker
30
47
 
31
48
func newLxcBroker(
32
 
        api APICalls, agentConfig agent.Config, managerConfig container.ManagerConfig,
 
49
        api APICalls,
 
50
        agentConfig agent.Config,
 
51
        managerConfig container.ManagerConfig,
33
52
        imageURLGetter container.ImageURLGetter,
 
53
        enableNAT bool,
 
54
        defaultMTU int,
34
55
) (environs.InstanceBroker, error) {
35
56
        manager, err := lxc.NewContainerManager(managerConfig, imageURLGetter)
36
57
        if err != nil {
40
61
                manager:     manager,
41
62
                api:         api,
42
63
                agentConfig: agentConfig,
 
64
                enableNAT:   enableNAT,
 
65
                defaultMTU:  defaultMTU,
43
66
        }, nil
44
67
}
45
68
 
47
70
        manager     container.Manager
48
71
        api         APICalls
49
72
        agentConfig agent.Config
 
73
        enableNAT   bool
 
74
        defaultMTU  int
50
75
}
51
76
 
52
77
// StartInstance is specified in the Broker interface.
53
78
func (broker *lxcBroker) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) {
54
 
        if args.MachineConfig.HasNetworks() {
 
79
        if args.InstanceConfig.HasNetworks() {
55
80
                return nil, errors.New("starting lxc containers with networks is not supported yet")
56
81
        }
57
82
        // TODO: refactor common code out of the container brokers.
58
 
        machineId := args.MachineConfig.MachineId
 
83
        machineId := args.InstanceConfig.MachineId
59
84
        lxcLogger.Infof("starting lxc container for machineId: %s", machineId)
60
85
 
61
86
        // Default to using the host network until we can configure.
63
88
        if bridgeDevice == "" {
64
89
                bridgeDevice = lxc.DefaultLxcBridge
65
90
        }
66
 
        network := container.BridgeNetworkConfig(bridgeDevice)
 
91
 
 
92
        if !environs.AddressAllocationEnabled() {
 
93
                logger.Debugf(
 
94
                        "address allocation feature flag not enabled; using DHCP for container %q",
 
95
                        machineId,
 
96
                )
 
97
        } else {
 
98
                logger.Debugf("trying to allocate static IP for container %q", machineId)
 
99
                allocatedInfo, err := configureContainerNetwork(
 
100
                        machineId,
 
101
                        bridgeDevice,
 
102
                        broker.api,
 
103
                        args.NetworkInfo,
 
104
                        true, // allocate a new address.
 
105
                        broker.enableNAT,
 
106
                )
 
107
                if err != nil {
 
108
                        // It's fine, just ignore it. The effect will be that the
 
109
                        // container won't have a static address configured.
 
110
                        logger.Infof("not allocating static IP for container %q: %v", machineId, err)
 
111
                } else {
 
112
                        args.NetworkInfo = allocatedInfo
 
113
                }
 
114
        }
 
115
        network := container.BridgeNetworkConfig(bridgeDevice, broker.defaultMTU, args.NetworkInfo)
67
116
 
68
117
        // The provisioner worker will provide all tools it knows about
69
118
        // (after applying explicitly specified constraints), which may
81
130
        }
82
131
 
83
132
        series := archTools.OneSeries()
84
 
        args.MachineConfig.MachineContainerType = instance.LXC
85
 
        args.MachineConfig.Tools = archTools[0]
 
133
        args.InstanceConfig.MachineContainerType = instance.LXC
 
134
        args.InstanceConfig.Tools = archTools[0]
86
135
 
87
136
        config, err := broker.api.ContainerConfig()
88
137
        if err != nil {
89
138
                lxcLogger.Errorf("failed to get container config: %v", err)
90
139
                return nil, err
91
140
        }
92
 
        if err := environs.PopulateMachineConfig(
93
 
                args.MachineConfig,
 
141
        storageConfig := &container.StorageConfig{
 
142
                AllowMount: config.AllowLXCLoopMounts,
 
143
        }
 
144
 
 
145
        if err := instancecfg.PopulateInstanceConfig(
 
146
                args.InstanceConfig,
94
147
                config.ProviderType,
95
148
                config.AuthorizedKeys,
96
149
                config.SSLHostnameVerification,
105
158
                return nil, err
106
159
        }
107
160
 
108
 
        inst, hardware, err := broker.manager.CreateContainer(args.MachineConfig, series, network)
 
161
        inst, hardware, err := broker.manager.CreateContainer(args.InstanceConfig, series, network, storageConfig)
109
162
        if err != nil {
110
163
                lxcLogger.Errorf("failed to start container: %v", err)
111
164
                return nil, err
144
197
        // Override the arch constraint with the arch of the host.
145
198
        return h.f.FindTools(v, series, &version.Current.Arch)
146
199
}
 
200
 
 
201
// resolvConf is the full path to the resolv.conf file on the local
 
202
// system. Defined here so it can be overriden for testing.
 
203
var resolvConf = "/etc/resolv.conf"
 
204
 
 
205
// localDNSServers parses the /etc/resolv.conf file (if available) and
 
206
// extracts all nameservers addresses, and the default search domain
 
207
// and returns them.
 
208
func localDNSServers() ([]network.Address, string, error) {
 
209
        file, err := os.Open(resolvConf)
 
210
        if os.IsNotExist(err) {
 
211
                return nil, "", nil
 
212
        } else if err != nil {
 
213
                return nil, "", errors.Annotatef(err, "cannot open %q", resolvConf)
 
214
        }
 
215
        defer file.Close()
 
216
 
 
217
        var addresses []network.Address
 
218
        var searchDomain string
 
219
        scanner := bufio.NewScanner(file)
 
220
        for scanner.Scan() {
 
221
                line := strings.TrimSpace(scanner.Text())
 
222
                if strings.HasPrefix(line, "#") {
 
223
                        // Skip comments.
 
224
                        continue
 
225
                }
 
226
                if strings.HasPrefix(line, "nameserver") {
 
227
                        address := strings.TrimPrefix(line, "nameserver")
 
228
                        // Drop comments after the address, if any.
 
229
                        if strings.Contains(address, "#") {
 
230
                                address = address[:strings.Index(address, "#")]
 
231
                        }
 
232
                        address = strings.TrimSpace(address)
 
233
                        addresses = append(addresses, network.NewAddress(address))
 
234
                }
 
235
                if strings.HasPrefix(line, "search") {
 
236
                        searchDomain = strings.TrimPrefix(line, "search")
 
237
                        // Drop comments after the domain, if any.
 
238
                        if strings.Contains(searchDomain, "#") {
 
239
                                searchDomain = searchDomain[:strings.Index(searchDomain, "#")]
 
240
                        }
 
241
                        searchDomain = strings.TrimSpace(searchDomain)
 
242
                }
 
243
        }
 
244
 
 
245
        if err := scanner.Err(); err != nil {
 
246
                return nil, "", errors.Annotatef(err, "cannot read DNS servers from %q", resolvConf)
 
247
        }
 
248
        return addresses, searchDomain, nil
 
249
}
 
250
 
 
251
// ipRouteAdd is the command template to add a static route for
 
252
// .ContainerIP using the .HostBridge device (usually lxcbr0 or virbr0).
 
253
var ipRouteAdd = mustParseTemplate("ipRouteAdd", `
 
254
ip route add {{.ContainerIP}} dev {{.HostBridge}}`[1:])
 
255
 
 
256
type IptablesRule struct {
 
257
        Table string
 
258
        Chain string
 
259
        Rule  string
 
260
}
 
261
 
 
262
var skipSNATRule = IptablesRule{
 
263
        // For EC2, to get internet access we need traffic to appear with
 
264
        // source address matching the container's host. For internal
 
265
        // traffic we want to keep the container IP because it is used
 
266
        // by some services. This rule sits above the SNAT rule, which
 
267
        // changes the source address of traffic to the container host IP
 
268
        // address, skipping this modification if the traffic destination
 
269
        // is inside the EC2 VPC.
 
270
        "nat",
 
271
        "POSTROUTING",
 
272
        "-d {{.SubnetCIDR}} -o {{.HostIF}} -j RETURN",
 
273
}
 
274
 
 
275
var iptablesRules = map[string]IptablesRule{
 
276
        // iptablesCheckSNAT is the command template to verify if a SNAT
 
277
        // rule already exists for the host NIC named .HostIF (usually
 
278
        // eth0) and source address .HostIP (usually eth0's address). We
 
279
        // need to check whether the rule exists because we only want to
 
280
        // add it once. Exit code 0 means the rule exists, 1 means it
 
281
        // doesn't
 
282
        "iptablesSNAT": {
 
283
                "nat",
 
284
                "POSTROUTING",
 
285
                "-o {{.HostIF}} -j SNAT --to-source {{.HostIP}}",
 
286
        }, "iptablesForwardOut": {
 
287
                // Ensure that we have ACCEPT rules that apply to the containers that
 
288
                // we are creating so any DROP rules added by libvirt while setting
 
289
                // up virbr0 further down the chain don't disrupt wanted traffic.
 
290
                "filter",
 
291
                "FORWARD",
 
292
                "-d {{.ContainerCIDR}} -o {{.HostBridge}} -j ACCEPT",
 
293
        }, "iptablesForwardIn": {
 
294
                "filter",
 
295
                "FORWARD",
 
296
                "-s {{.ContainerCIDR}} -i {{.HostBridge}} -j ACCEPT",
 
297
        }}
 
298
 
 
299
// mustParseTemplate works like template.Parse, but panics on error.
 
300
func mustParseTemplate(name, source string) *template.Template {
 
301
        templ, err := template.New(name).Parse(source)
 
302
        if err != nil {
 
303
                panic(err.Error())
 
304
        }
 
305
        return templ
 
306
}
 
307
 
 
308
// mustExecTemplate works like template.Parse followed by template.Execute,
 
309
// but panics on error.
 
310
func mustExecTemplate(name, tmpl string, data interface{}) string {
 
311
        t := mustParseTemplate(name, tmpl)
 
312
        var buf bytes.Buffer
 
313
        if err := t.Execute(&buf, data); err != nil {
 
314
                panic(err.Error())
 
315
        }
 
316
        return buf.String()
 
317
}
 
318
 
 
319
// runTemplateCommand executes the given template with the given data,
 
320
// which generates a command to execute. If exitNonZeroOK is true, no
 
321
// error is returned if the exit code is not 0, otherwise an error is
 
322
// returned.
 
323
func runTemplateCommand(t *template.Template, exitNonZeroOK bool, data interface{}) (
 
324
        exitCode int, err error,
 
325
) {
 
326
        // Clone the template to ensure the original won't be changed.
 
327
        cloned, err := t.Clone()
 
328
        if err != nil {
 
329
                return -1, errors.Annotatef(err, "cannot clone command template %q", t.Name())
 
330
        }
 
331
        var buf bytes.Buffer
 
332
        if err := cloned.Execute(&buf, data); err != nil {
 
333
                return -1, errors.Annotatef(err, "cannot execute command template %q", t.Name())
 
334
        }
 
335
        command := buf.String()
 
336
        logger.Debugf("running command %q", command)
 
337
        result, err := exec.RunCommands(exec.RunParams{Commands: command})
 
338
        if err != nil {
 
339
                return -1, errors.Annotatef(err, "cannot run command %q", command)
 
340
        }
 
341
        exitCode = result.Code
 
342
        stdout := string(result.Stdout)
 
343
        stderr := string(result.Stderr)
 
344
        logger.Debugf(
 
345
                "command %q returned code=%d, stdout=%q, stderr=%q",
 
346
                command, exitCode, stdout, stderr,
 
347
        )
 
348
        if exitCode != 0 {
 
349
                if exitNonZeroOK {
 
350
                        return exitCode, nil
 
351
                }
 
352
                return exitCode, errors.Errorf(
 
353
                        "command %q failed with exit code %d",
 
354
                        command, exitCode,
 
355
                )
 
356
        }
 
357
        return 0, nil
 
358
}
 
359
 
 
360
// setupRoutesAndIPTables sets up on the host machine the needed
 
361
// iptables rules and static routes for an addressable container.
 
362
var setupRoutesAndIPTables = func(
 
363
        primaryNIC string,
 
364
        primaryAddr network.Address,
 
365
        bridgeName string,
 
366
        ifaceInfo []network.InterfaceInfo,
 
367
        enableNAT bool,
 
368
) error {
 
369
 
 
370
        if primaryNIC == "" || primaryAddr.Value == "" || bridgeName == "" || len(ifaceInfo) == 0 {
 
371
                return errors.Errorf("primaryNIC, primaryAddr, bridgeName, and ifaceInfo must be all set")
 
372
        }
 
373
 
 
374
        for _, iface := range ifaceInfo {
 
375
                containerIP := iface.Address.Value
 
376
                if containerIP == "" {
 
377
                        return errors.Errorf("container IP %q must be set", containerIP)
 
378
                }
 
379
                data := struct {
 
380
                        HostIF        string
 
381
                        HostIP        string
 
382
                        HostBridge    string
 
383
                        ContainerIP   string
 
384
                        ContainerCIDR string
 
385
                        SubnetCIDR    string
 
386
                }{primaryNIC, primaryAddr.Value, bridgeName, containerIP, iface.CIDR, iface.CIDR}
 
387
 
 
388
                var addRuleIfDoesNotExist = func(name string, rule IptablesRule) error {
 
389
                        check := mustExecTemplate("rule", "iptables -t {{.Table}} -C {{.Chain}} {{.Rule}}", rule)
 
390
                        t := mustParseTemplate(name+"Check", check)
 
391
 
 
392
                        code, err := runTemplateCommand(t, true, data)
 
393
                        if err != nil {
 
394
                                return errors.Trace(err)
 
395
                        }
 
396
                        switch code {
 
397
                        case 0:
 
398
                        // Rule does exist. Do nothing
 
399
                        case 1:
 
400
                                // Rule does not exist, add it. We insert the rule at the top of the list so it precedes any
 
401
                                // REJECT rules.
 
402
                                action := mustExecTemplate("action", "iptables -t {{.Table}} -I {{.Chain}} 1 {{.Rule}}", rule)
 
403
                                t = mustParseTemplate(name+"Add", action)
 
404
                                _, err = runTemplateCommand(t, false, data)
 
405
                                if err != nil {
 
406
                                        return errors.Trace(err)
 
407
                                }
 
408
                        default:
 
409
                                // Unexpected code - better report it.
 
410
                                return errors.Errorf("iptables failed with unexpected exit code %d", code)
 
411
                        }
 
412
                        return nil
 
413
                }
 
414
 
 
415
                for name, rule := range iptablesRules {
 
416
                        if !enableNAT && name == "iptablesSNAT" {
 
417
                                // Do not add the SNAT rule if we shouldn't enable
 
418
                                // NAT.
 
419
                                continue
 
420
                        }
 
421
                        if err := addRuleIfDoesNotExist(name, rule); err != nil {
 
422
                                return err
 
423
                        }
 
424
                }
 
425
 
 
426
                // TODO(dooferlad): subnets should be a list of subnets in the EC2 VPC and
 
427
                // should be empty for MAAS. See bug http://pad.lv/1443942
 
428
                if enableNAT {
 
429
                        // Only add the following hack to allow AWS egress traffic
 
430
                        // for hosted containers to work.
 
431
                        subnets := []string{data.HostIP + "/16"}
 
432
                        for _, subnet := range subnets {
 
433
                                data.SubnetCIDR = subnet
 
434
                                if err := addRuleIfDoesNotExist("skipSNAT", skipSNATRule); err != nil {
 
435
                                        return err
 
436
                                }
 
437
                        }
 
438
                }
 
439
 
 
440
                code, err := runTemplateCommand(ipRouteAdd, false, data)
 
441
                // Ignore errors if the exit code was 2, which signals that the route was not added
 
442
                // because it already exists.
 
443
                if code != 2 && err != nil {
 
444
                        return errors.Trace(err)
 
445
                }
 
446
                if code == 2 {
 
447
                        logger.Tracef("route already exists - not added")
 
448
                } else {
 
449
                        logger.Tracef("route added: container uses host network interface")
 
450
                }
 
451
        }
 
452
        logger.Infof("successfully configured iptables and routes for container interfaces")
 
453
 
 
454
        return nil
 
455
}
 
456
 
 
457
var (
 
458
        netInterfaces  = net.Interfaces
 
459
        interfaceAddrs = (*net.Interface).Addrs
 
460
)
 
461
 
 
462
// discoverPrimaryNIC returns the name of the first network interface
 
463
// on the machine which is up and has address, along with the first
 
464
// address it has.
 
465
func discoverPrimaryNIC() (string, network.Address, error) {
 
466
        interfaces, err := netInterfaces()
 
467
        if err != nil {
 
468
                return "", network.Address{}, errors.Annotatef(err, "cannot get network interfaces")
 
469
        }
 
470
        logger.Tracef("trying to discover primary network interface")
 
471
        for _, iface := range interfaces {
 
472
                if iface.Flags&net.FlagLoopback != 0 {
 
473
                        // Skip the loopback.
 
474
                        logger.Tracef("not using loopback interface %q", iface.Name)
 
475
                        continue
 
476
                }
 
477
                if iface.Flags&net.FlagUp != 0 {
 
478
                        // Possibly the primary, but ensure it has an address as
 
479
                        // well.
 
480
                        logger.Tracef("verifying interface %q has addresses", iface.Name)
 
481
                        addrs, err := interfaceAddrs(&iface)
 
482
                        if err != nil {
 
483
                                return "", network.Address{}, errors.Annotatef(err, "cannot get %q addresses", iface.Name)
 
484
                        }
 
485
                        if len(addrs) > 0 {
 
486
                                // We found it.
 
487
                                // Check if it's an IP or a CIDR.
 
488
                                addr := addrs[0].String()
 
489
                                ip := net.ParseIP(addr)
 
490
                                if ip == nil {
 
491
                                        // Try a CIDR.
 
492
                                        ip, _, err = net.ParseCIDR(addr)
 
493
                                        if err != nil {
 
494
                                                return "", network.Address{}, errors.Annotatef(err, "cannot parse address %q", addr)
 
495
                                        }
 
496
                                }
 
497
                                addr = ip.String()
 
498
 
 
499
                                logger.Tracef("primary network interface is %q, address %q", iface.Name, addr)
 
500
                                return iface.Name, network.NewAddress(addr), nil
 
501
                        }
 
502
                }
 
503
        }
 
504
        return "", network.Address{}, errors.Errorf("cannot detect the primary network interface")
 
505
}
 
506
 
 
507
// MACAddressTemplate is used to generate a unique MAC address for a
 
508
// container. Every 'x' is replaced by a random hexadecimal digit,
 
509
// while the rest is kept as-is.
 
510
const MACAddressTemplate = "00:16:3e:xx:xx:xx"
 
511
 
 
512
// configureContainerNetworking tries to allocate a static IP address
 
513
// for the given containerId using the provisioner API, when
 
514
// allocateAddress is true. Otherwise it configures the container with
 
515
// an already allocated address, when allocateAddress is false (e.g.
 
516
// after a host reboot). If the API call fails, it's not critical -
 
517
// just a warning, and it won't cause StartInstance to fail.
 
518
func configureContainerNetwork(
 
519
        containerId, bridgeDevice string,
 
520
        apiFacade APICalls,
 
521
        ifaceInfo []network.InterfaceInfo,
 
522
        allocateAddress bool,
 
523
        enableNAT bool,
 
524
) (finalIfaceInfo []network.InterfaceInfo, err error) {
 
525
        defer func() {
 
526
                if err != nil {
 
527
                        logger.Warningf(
 
528
                                "failed configuring a static IP for container %q: %v",
 
529
                                containerId, err,
 
530
                        )
 
531
                }
 
532
        }()
 
533
 
 
534
        if len(ifaceInfo) != 0 {
 
535
                // When we already have interface info, don't overwrite it.
 
536
                return nil, nil
 
537
        }
 
538
 
 
539
        var primaryNIC string
 
540
        var primaryAddr network.Address
 
541
        primaryNIC, primaryAddr, err = discoverPrimaryNIC()
 
542
        if err != nil {
 
543
                return nil, errors.Trace(err)
 
544
        }
 
545
 
 
546
        if allocateAddress {
 
547
                logger.Debugf("trying to allocate a static IP for container %q", containerId)
 
548
                finalIfaceInfo, err = apiFacade.PrepareContainerInterfaceInfo(names.NewMachineTag(containerId))
 
549
        } else {
 
550
                logger.Debugf("getting allocated static IP for container %q", containerId)
 
551
                finalIfaceInfo, err = apiFacade.GetContainerInterfaceInfo(names.NewMachineTag(containerId))
 
552
        }
 
553
        if err != nil {
 
554
                return nil, errors.Trace(err)
 
555
        }
 
556
        logger.Debugf("container interface info result %#v", finalIfaceInfo)
 
557
 
 
558
        // Populate ConfigType and DNSServers as needed.
 
559
        var dnsServers []network.Address
 
560
        var searchDomain string
 
561
        dnsServers, searchDomain, err = localDNSServers()
 
562
        if err != nil {
 
563
                return nil, errors.Trace(err)
 
564
        }
 
565
        // Generate the final configuration for each container interface.
 
566
        for i, _ := range finalIfaceInfo {
 
567
                // Always start at the first device index and generate the
 
568
                // interface name based on that. We need to do this otherwise
 
569
                // the container will inherit the host's device index and
 
570
                // interface name.
 
571
                finalIfaceInfo[i].DeviceIndex = i
 
572
                finalIfaceInfo[i].InterfaceName = fmt.Sprintf("eth%d", i)
 
573
                finalIfaceInfo[i].MACAddress = MACAddressTemplate
 
574
                finalIfaceInfo[i].ConfigType = network.ConfigStatic
 
575
                finalIfaceInfo[i].DNSServers = dnsServers
 
576
                finalIfaceInfo[i].DNSSearch = searchDomain
 
577
                finalIfaceInfo[i].GatewayAddress = primaryAddr
 
578
        }
 
579
        err = setupRoutesAndIPTables(
 
580
                primaryNIC,
 
581
                primaryAddr,
 
582
                bridgeDevice,
 
583
                finalIfaceInfo,
 
584
                enableNAT,
 
585
        )
 
586
        if err != nil {
 
587
                return nil, errors.Trace(err)
 
588
        }
 
589
        return finalIfaceInfo, nil
 
590
}
 
591
 
 
592
// MaintainInstance checks that the container's host has the required iptables and routing
 
593
// rules to make the container visible to both the host and other machines on the same subnet.
 
594
func (broker *lxcBroker) MaintainInstance(args environs.StartInstanceParams) error {
 
595
        machineId := args.InstanceConfig.MachineId
 
596
        if !environs.AddressAllocationEnabled() {
 
597
                lxcLogger.Debugf("address allocation disabled: Not running maintenance for lxc container with machineId: %s",
 
598
                        machineId)
 
599
                return nil
 
600
        }
 
601
 
 
602
        lxcLogger.Debugf("running maintenance for lxc container with machineId: %s", machineId)
 
603
 
 
604
        // Default to using the host network until we can configure.
 
605
        bridgeDevice := broker.agentConfig.Value(agent.LxcBridge)
 
606
        if bridgeDevice == "" {
 
607
                bridgeDevice = lxc.DefaultLxcBridge
 
608
        }
 
609
        _, err := configureContainerNetwork(
 
610
                machineId,
 
611
                bridgeDevice,
 
612
                broker.api,
 
613
                args.NetworkInfo,
 
614
                false, // don't allocate a new address.
 
615
                broker.enableNAT,
 
616
        )
 
617
        return err
 
618
}