The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/4113

This e-mail was sent by the LXC bot, direct replies will not reach the author
unless they happen to be subscribed to this list.

=== Description (from pull-request) ===

From 42e329a09c67f9d8a10b621ba13d79993c1de608 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <[email protected]>
Date: Wed, 13 Dec 2017 03:17:25 -0500
Subject: [PATCH 1/4] lxd/maas: Add MAAS integration package
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <[email protected]>
---
 lxd/maas/controller.go | 377 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 377 insertions(+)
 create mode 100644 lxd/maas/controller.go

diff --git a/lxd/maas/controller.go b/lxd/maas/controller.go
new file mode 100644
index 000000000..59eb3dc3d
--- /dev/null
+++ b/lxd/maas/controller.go
@@ -0,0 +1,377 @@
+package maas
+
+import (
+       "fmt"
+       "net/url"
+
+       "github.com/juju/gomaasapi"
+)
+
+// Controller represents a MAAS server's machine functions
+type Controller struct {
+       url string
+
+       srv     gomaasapi.Controller
+       srvRaw  gomaasapi.Client
+       machine gomaasapi.Machine
+}
+
+// ContainerInterface represents a MAAS connected network interface on the 
container
+type ContainerInterface struct {
+       Name       string
+       MACAddress string
+       Subnets    []ContainerInterfaceSubnet
+}
+
+// ContainerInterfaceSubnet represents an interface's subscription to a MAAS 
subnet
+type ContainerInterfaceSubnet struct {
+       Name    string
+       Address string
+}
+
+func parseInterfaces(interfaces []ContainerInterface) 
(map[string]ContainerInterface, error) {
+       // Sanity checks
+       if len(interfaces) == 0 {
+               return nil, fmt.Errorf("At least one interface must be 
provided")
+       }
+
+       // Parse the MACs and interfaces
+       macInterfaces := map[string]ContainerInterface{}
+       for _, iface := range interfaces {
+               _, ok := macInterfaces[iface.MACAddress]
+               if ok {
+                       return nil, fmt.Errorf("MAAS doesn't allow duplicate 
MAC addresses")
+               }
+
+               if iface.MACAddress == "" {
+                       return nil, fmt.Errorf("Interfaces must have a MAC 
address")
+               }
+
+               if len(iface.Subnets) == 0 {
+                       return nil, fmt.Errorf("Interfaces must have at least 
one subnet")
+               }
+
+               macInterfaces[iface.MACAddress] = iface
+       }
+
+       return macInterfaces, nil
+}
+
+// NewController returns a new Controller using the specific MAAS server and 
machine
+func NewController(url string, key string, machine string) (*Controller, 
error) {
+       baseURL := fmt.Sprintf("%s/api/2.0/", url)
+
+       // Connect to MAAS
+       srv, err := gomaasapi.NewController(gomaasapi.ControllerArgs{
+               BaseURL: baseURL,
+               APIKey:  key,
+       })
+       if err != nil {
+               return nil, err
+       }
+
+       srvRaw, err := gomaasapi.NewAuthenticatedClient(baseURL, key)
+       if err != nil {
+               return nil, err
+       }
+
+       // Find the right machine
+       machines, err := srv.Machines(gomaasapi.MachinesArgs{Hostnames: 
[]string{machine}})
+       if err != nil {
+               return nil, err
+       }
+
+       if len(machines) != 1 {
+               return nil, fmt.Errorf("Couldn't find the specified machine: 
%s", machine)
+       }
+
+       // Setup the struct
+       c := Controller{}
+       c.srv = srv
+       c.srvRaw = *srvRaw
+       c.machine = machines[0]
+       c.url = baseURL
+
+       return &c, err
+}
+
+func (c *Controller) getDevice(name string) (gomaasapi.Device, error) {
+       devs, err := c.machine.Devices(gomaasapi.DevicesArgs{Hostname: 
[]string{name}})
+       if err != nil {
+               return nil, err
+       }
+
+       if len(devs) != 1 {
+               return nil, fmt.Errorf("Couldn't find the specified container: 
%s", name)
+       }
+
+       return devs[0], nil
+}
+
+func (c *Controller) getSubnets() (map[string]gomaasapi.Subnet, error) {
+       // Get all the spaces
+       spaces, err := c.srv.Spaces()
+       if err != nil {
+               return nil, err
+       }
+
+       // Get all the subnets
+       subnets := map[string]gomaasapi.Subnet{}
+       for _, space := range spaces {
+               for _, subnet := range space.Subnets() {
+                       subnets[subnet.Name()] = subnet
+               }
+       }
+
+       return subnets, nil
+}
+
+// CreateContainer defines a new MAAS device for the controller
+func (c *Controller) CreateContainer(name string, interfaces 
[]ContainerInterface) error {
+       // Parse the provided interfaces
+       macInterfaces, err := parseInterfaces(interfaces)
+       if err != nil {
+               return err
+       }
+
+       // Get all the subnets
+       subnets, err := c.getSubnets()
+       if err != nil {
+               return err
+       }
+
+       // Create the device and first interface
+       device, err := c.machine.CreateDevice(gomaasapi.CreateMachineDeviceArgs{
+               Hostname:      name,
+               InterfaceName: interfaces[0].Name,
+               MACAddress:    interfaces[0].MACAddress,
+               VLAN:          subnets[interfaces[0].Subnets[0].Name].VLAN(),
+       })
+       if err != nil {
+               return err
+       }
+
+       // Wipe the container entry if anything fails
+       success := false
+       defer func() {
+               if success == true {
+                       return
+               }
+
+               c.DeleteContainer(name)
+       }()
+
+       // Create the rest of the interfaces
+       for _, iface := range interfaces[1:] {
+               _, err := device.CreateInterface(gomaasapi.CreateInterfaceArgs{
+                       Name:       iface.Name,
+                       MACAddress: iface.MACAddress,
+                       VLAN:       subnets[iface.Subnets[0].Name].VLAN(),
+               })
+               if err != nil {
+                       return err
+               }
+       }
+
+       // Get a fresh copy of the device
+       device, err = c.getDevice(name)
+       if err != nil {
+               return err
+       }
+
+       // Setup the interfaces
+       for _, entry := range device.InterfaceSet() {
+               // Get our record
+               iface, ok := macInterfaces[entry.MACAddress()]
+               if !ok {
+                       return fmt.Errorf("MAAS created an interface with a bad 
MAC: %s", entry.MACAddress())
+               }
+
+               // Add the subnets
+               for _, subnet := range iface.Subnets {
+                       err := entry.LinkSubnet(gomaasapi.LinkSubnetArgs{
+                               Mode:      gomaasapi.LinkModeStatic,
+                               Subnet:    subnets[subnet.Name],
+                               IPAddress: subnet.Address,
+                       })
+                       if err != nil {
+                               return err
+                       }
+               }
+       }
+
+       success = true
+       return nil
+}
+
+// DefinedContainer returns true if the container is defined in MAAS
+func (c *Controller) DefinedContainer(name string) (bool, error) {
+       devs, err := c.machine.Devices(gomaasapi.DevicesArgs{Hostname: 
[]string{name}})
+       if err != nil {
+               return false, err
+       }
+
+       if len(devs) == 1 {
+               return true, nil
+       }
+
+       return false, nil
+}
+
+// UpdateContainer updates the MAAS device's interfaces with the new provided 
state
+func (c *Controller) UpdateContainer(name string, interfaces 
[]ContainerInterface) error {
+       // Parse the provided interfaces
+       macInterfaces, err := parseInterfaces(interfaces)
+       if err != nil {
+               return err
+       }
+
+       // Get all the subnets
+       subnets, err := c.getSubnets()
+       if err != nil {
+               return err
+       }
+
+       device, err := c.getDevice(name)
+       if err != nil {
+               return err
+       }
+
+       // Iterate over existing interfaces, drop all removed ones and update 
existing ones
+       existingInterfaces := map[string]gomaasapi.Interface{}
+       for _, entry := range device.InterfaceSet() {
+               // Check if the interface has been removed from the container
+               iface, ok := macInterfaces[entry.MACAddress()]
+               if !ok {
+                       // Delete the interface in MAAS
+                       err = entry.Delete()
+                       if err != nil {
+                               return err
+                       }
+
+                       continue
+               }
+
+               // Update the subnets
+               existingSubnets := map[string]gomaasapi.Subnet{}
+               for _, link := range entry.Links() {
+                       // Check if the MAAS subnet matches any of the 
container's
+                       found := false
+                       for _, subnet := range iface.Subnets {
+                               if subnet.Name == link.Subnet().Name() {
+                                       if subnet.Address == "" || 
subnet.Address == link.IPAddress() {
+                                               found = true
+                                       }
+                                       break
+                               }
+                       }
+
+                       // If no exact match could be found, remove it from MAAS
+                       if !found {
+                               err = entry.UnlinkSubnet(link.Subnet())
+                               if err != nil {
+                                       return err
+                               }
+
+                               continue
+                       }
+
+                       // Record the existing up to date subnet
+                       existingSubnets[link.Subnet().Name()] = link.Subnet()
+               }
+
+               // Add any missing (or updated) subnet to MAAS
+               for _, subnet := range iface.Subnets {
+                       // Check that it's not configured yet
+                       _, ok := existingSubnets[subnet.Name]
+                       if ok {
+                               continue
+                       }
+
+                       // Add the link
+                       err := entry.LinkSubnet(gomaasapi.LinkSubnetArgs{
+                               Mode:      gomaasapi.LinkModeStatic,
+                               Subnet:    subnets[subnet.Name],
+                               IPAddress: subnet.Address,
+                       })
+                       if err != nil {
+                               return err
+                       }
+               }
+
+               // Record the interface has being configured
+               existingInterfaces[entry.MACAddress()] = entry
+       }
+
+       // Iterate over expected interfaces, add any missing one
+       for _, iface := range macInterfaces {
+               _, ok := existingInterfaces[iface.MACAddress]
+               if ok {
+                       // We already have it so just move on
+                       continue
+               }
+
+               // Create the new interface
+               entry, err := 
device.CreateInterface(gomaasapi.CreateInterfaceArgs{
+                       Name:       iface.Name,
+                       MACAddress: iface.MACAddress,
+                       VLAN:       subnets[iface.Subnets[0].Name].VLAN(),
+               })
+               if err != nil {
+                       return err
+               }
+
+               // Add the subnets
+               for _, subnet := range iface.Subnets {
+                       err := entry.LinkSubnet(gomaasapi.LinkSubnetArgs{
+                               Mode:      gomaasapi.LinkModeStatic,
+                               Subnet:    subnets[subnet.Name],
+                               IPAddress: subnet.Address,
+                       })
+                       if err != nil {
+                               return err
+                       }
+               }
+       }
+
+       return nil
+}
+
+// RenameContainer renames the MAAS device for the container without releasing 
any allocation
+func (c *Controller) RenameContainer(name string, newName string) error {
+       device, err := c.getDevice(name)
+       if err != nil {
+               return err
+       }
+
+       // FIXME: We should convince the Juju folks to implement an Update() 
method on Device
+       uri, err := url.Parse(fmt.Sprintf("%s/devices/%s/", c.url, 
device.SystemID()))
+       if err != nil {
+               return err
+       }
+
+       values := url.Values{}
+       values.Set("hostname", newName)
+
+       _, err = c.srvRaw.Put(uri, values)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// DeleteContainer removes the MAAS device for the container
+func (c *Controller) DeleteContainer(name string) error {
+       device, err := c.getDevice(name)
+       if err != nil {
+               return err
+       }
+
+       err = device.Delete()
+       if err != nil {
+               return err
+       }
+
+       return nil
+}

From 8635340b25e554a04ad5447bc77cfb16bb51252b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <[email protected]>
Date: Fri, 15 Dec 2017 01:36:19 -0500
Subject: [PATCH 2/4] Add the new MAAS configuration keys
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <[email protected]>
---
 config/bash/lxd-client |  3 ++-
 doc/api-extensions.md  |  6 ++++++
 doc/containers.md      | 19 +++++++++++++++++++
 doc/server.md          |  4 ++++
 lxd/container.go       |  4 ++++
 lxd/daemon_config.go   |  4 ++++
 lxd/node/config.go     |  3 +++
 shared/version/api.go  |  1 +
 8 files changed, 43 insertions(+), 1 deletion(-)

diff --git a/config/bash/lxd-client b/config/bash/lxd-client
index d7f9fb26f..e74dd8d95 100644
--- a/config/bash/lxd-client
+++ b/config/bash/lxd-client
@@ -69,7 +69,8 @@ _have lxc && {
       core.https_allowed_origin core.macaroon.endpoint core.proxy_https \
       core.proxy_http core.proxy_ignore_hosts core.trust_password \
       images.auto_update_cached images.auto_update_interval \
-      images.compression_algorithm images.remote_cache_expiry"
+      images.compression_algorithm images.remote_cache_expiry \
+      maas.api.url maas.api.key maas.machine"
 
     container_keys="boot.autostart boot.autostart.delay \
       boot.autostart.priority boot.stop.priority \
diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index b49c0ed74..a6bd1ae1e 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -379,3 +379,9 @@ This adds support for optimized memory transfer during live 
migration.
 
 ## infiniband
 This adds support to use infiniband network devices.
+
+## maas\_network
+This adds support for MAAS network integration.
+
+When configured at the daemon level, it's then possible to attach a "nic"
+device to a particular MAAS subnet.
diff --git a/doc/containers.md b/doc/containers.md
index 2a5496be9..cfd23287b 100644
--- a/doc/containers.md
+++ b/doc/containers.md
@@ -195,6 +195,8 @@ vlan                    | integer   | -                 | 
no        | macvlan, p
 ipv4.address            | string    | -                 | no        | bridged  
                         | network                                | An IPv4 
address to assign to the container through DHCP
 ipv6.address            | string    | -                 | no        | bridged  
                         | network                                | An IPv6 
address to assign to the container through DHCP
 security.mac\_filtering | boolean   | false             | no        | bridged  
                         | network                                | Prevent the 
container from spoofing another's MAC address
+maas.subnet.ipv4        | string    | -                 | no        | bridged, 
macvlan, physical, sriov | maas\_network                          | MAAS IPv4 
subnet to register the container in
+maas.subnet.ipv6        | string    | -                 | no        | bridged, 
macvlan, physical, sriov | maas\_network                          | MAAS IPv6 
subnet to register the container in
 
 #### bridged or macvlan for connection to physical network
 The `bridged` and `macvlan` interface types can both be used to connect
@@ -237,6 +239,23 @@ lxc config device add <container> <device-name> nic 
nictype=sriov parent=<sriov-
 To tell LXD to use a specific unused VF add the `host_name` property and pass
 it the name of the enabled VF.
 
+
+#### MAAS integration
+If you're using MAAS to manage the physical network under your LXD host
+and want to attach your containers directly to a MAAS managed network,
+LXD can be configured to interact with MAAS so that it can track your
+containers.
+
+At the daemon level, you must configure `maas.api.url` and
+`maas.api.key`, then set the `maas.subnet.ipv4` and/or
+`maas.subnet.ipv6` keys on the container or profile's `nic` entry.
+
+This will have LXD register all your containers with MAAS, giving them
+proper DHCP leases and DNS records.
+
+If you set the `ipv4.address` or `ipv6.address` keys on the nic, then
+those will be registered as static assignments in MAAS too.
+
 ### Type: infiniband
 LXD supports two different kind of network types for infiniband devices:
 
diff --git a/doc/server.md b/doc/server.md
index ab565b7c0..8426246ab 100644
--- a/doc/server.md
+++ b/doc/server.md
@@ -6,6 +6,7 @@ currently supported:
 
  - `core` (core daemon configuration)
  - `images` (image configuration)
+ - `maas` (MAAS integration)
 
 Key                             | Type      | Default   | API extension        
    | Description
 :--                             | :---      | :------   | :------------        
    | :----------
@@ -23,6 +24,9 @@ images.auto\_update\_cached     | boolean   | true      | -
 images.auto\_update\_interval   | integer   | 6         | -                    
    | Interval in hours at which to look for update to cached images (0 
disables it)
 images.compression\_algorithm   | string    | gzip      | -                    
    | Compression algorithm to use for new images (bzip2, gzip, lzma, xz or 
none)
 images.remote\_cache\_expiry    | integer   | 10        | -                    
    | Number of days after which an unused cached remote image will be flushed
+maas.api.key                    | string    | -         | maas\_network        
    | API key to manage MAAS
+maas.api.url                    | string    | -         | maas\_network        
    | URL of the MAAS server
+maas.machine                    | string    | hostname  | maas\_network        
    | Name of this LXD host in MAAS
 
 Those keys can be set using the lxc tool with:
 
diff --git a/lxd/container.go b/lxd/container.go
index 9b9d89258..53b4db3c3 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -135,6 +135,10 @@ func containerValidDeviceConfigKey(t, k string) bool {
                        return true
                case "security.mac_filtering":
                        return true
+               case "maas.subnet.ipv4":
+                       return true
+               case "maas.subnet.ipv6":
+                       return true
                default:
                        return false
                }
diff --git a/lxd/daemon_config.go b/lxd/daemon_config.go
index c0b7fa722..79a80b4a8 100644
--- a/lxd/daemon_config.go
+++ b/lxd/daemon_config.go
@@ -196,6 +196,10 @@ func daemonConfigInit(db *sql.DB) error {
                "images.compression_algorithm": {valueType: "string", 
validator: daemonConfigValidateCompression, defaultValue: "gzip"},
                "images.remote_cache_expiry":   {valueType: "int", 
defaultValue: "10", trigger: daemonConfigTriggerExpiry},
 
+               "maas.api.key": {valueType: "string"},
+               "maas.api.url": {valueType: "string"},
+               "maas.machine": {valueType: "string"},
+
                // Keys deprecated since the implementation of the storage api.
                "storage.lvm_fstype":           {valueType: "string", 
defaultValue: "ext4", validValues: []string{"btrfs", "ext4", "xfs"}, validator: 
storageDeprecatedKeys},
                "storage.lvm_mount_options":    {valueType: "string", 
defaultValue: "discard", validator: storageDeprecatedKeys},
diff --git a/lxd/node/config.go b/lxd/node/config.go
index 25f3d1f63..7aa6371ba 100644
--- a/lxd/node/config.go
+++ b/lxd/node/config.go
@@ -91,6 +91,9 @@ var ConfigSchema = config.Schema{
        "images.auto_update_interval":    {},
        "images.compression_algorithm":   {},
        "images.remote_cache_expiry":     {},
+       "maas.api.key":                   {},
+       "maas.api.url":                   {},
+       "maas.machine":                   {},
        "storage.lvm_fstype":             {},
        "storage.lvm_mount_options":      {},
        "storage.lvm_thinpool_name":      {},
diff --git a/shared/version/api.go b/shared/version/api.go
index 07bfdd9ea..eae184c59 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -84,4 +84,5 @@ var APIExtensions = []string{
        "restrict_devlxd",
        "migration_pre_copy",
        "infiniband",
+       "maas_network",
 }

From 4ecc1523f2f4bfe7979f53304e7ca145df7129fe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <[email protected]>
Date: Fri, 15 Dec 2017 02:37:55 -0500
Subject: [PATCH 3/4] lxd: Integrate MAAS controller with daemon
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <[email protected]>
---
 lxd/daemon.go        | 41 ++++++++++++++++++++++++++++++++++++++++-
 lxd/daemon_config.go | 30 +++++++++++++++++++++++++++---
 lxd/state/state.go   | 13 ++++++++-----
 3 files changed, 75 insertions(+), 9 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 3a22932cb..5262cdce3 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -27,6 +27,7 @@ import (
 
        "github.com/lxc/lxd/lxd/db"
        "github.com/lxc/lxd/lxd/endpoints"
+       "github.com/lxc/lxd/lxd/maas"
        "github.com/lxc/lxd/lxd/state"
        "github.com/lxc/lxd/lxd/sys"
        "github.com/lxc/lxd/lxd/task"
@@ -43,6 +44,7 @@ type Daemon struct {
        clientCerts  []x509.Certificate
        os           *sys.OS
        db           *db.Node
+       maas         *maas.Controller
        readyChan    chan bool
        shutdownChan chan bool
 
@@ -172,7 +174,7 @@ func isJSONRequest(r *http.Request) bool {
 
 // State creates a new State instance liked to our internal db and os.
 func (d *Daemon) State() *state.State {
-       return state.NewState(d.db, d.os)
+       return state.NewState(d.db, d.maas, d.os)
 }
 
 // UnixSocket returns the full path to the unix.socket file that this daemon is
@@ -418,6 +420,14 @@ func (d *Daemon) init() error {
                return err
        }
 
+       err = d.setupMAASController(
+               daemonConfig["maas.api.url"].Get(),
+               daemonConfig["maas.api.key"].Get(),
+               daemonConfig["maas.machine"].Get())
+       if err != nil {
+               return err
+       }
+
        /* Setup the web server */
        certInfo, err := shared.KeyPairAndCA(d.os.VarDir, "server", 
shared.CertServer)
        if err != nil {
@@ -587,6 +597,35 @@ func (d *Daemon) setupExternalAuthentication(authEndpoint 
string) error {
        return nil
 }
 
+// Setup MAAS
+func (d *Daemon) setupMAASController(server string, key string, machine 
string) error {
+       var err error
+       d.maas = nil
+
+       // Default the machine name to the hostname
+       if machine == "" {
+               machine, err = os.Hostname()
+               if err != nil {
+                       return err
+               }
+       }
+
+       // We need both URL and key, otherwise disable MAAS
+       if server == "" || key == "" {
+               return nil
+       }
+
+       // Get a new controller struct
+       controller, err := maas.NewController(server, key, machine)
+       if err != nil {
+               d.maas = nil
+               return err
+       }
+
+       d.maas = controller
+       return nil
+}
+
 // Create a database connection and perform any updates needed.
 func initializeDbObject(d *Daemon) error {
        // NOTE: we use the legacyPatches parameter to run a few
diff --git a/lxd/daemon_config.go b/lxd/daemon_config.go
index 79a80b4a8..741e9a624 100644
--- a/lxd/daemon_config.go
+++ b/lxd/daemon_config.go
@@ -196,9 +196,9 @@ func daemonConfigInit(db *sql.DB) error {
                "images.compression_algorithm": {valueType: "string", 
validator: daemonConfigValidateCompression, defaultValue: "gzip"},
                "images.remote_cache_expiry":   {valueType: "int", 
defaultValue: "10", trigger: daemonConfigTriggerExpiry},
 
-               "maas.api.key": {valueType: "string"},
-               "maas.api.url": {valueType: "string"},
-               "maas.machine": {valueType: "string"},
+               "maas.api.key": {valueType: "string", setter: 
daemonConfigSetMAAS},
+               "maas.api.url": {valueType: "string", setter: 
daemonConfigSetMAAS},
+               "maas.machine": {valueType: "string", setter: 
daemonConfigSetMAAS},
 
                // Keys deprecated since the implementation of the storage api.
                "storage.lvm_fstype":           {valueType: "string", 
defaultValue: "ext4", validValues: []string{"btrfs", "ext4", "xfs"}, validator: 
storageDeprecatedKeys},
@@ -319,6 +319,30 @@ func daemonConfigSetProxy(d *Daemon, key string, value 
string) (string, error) {
        return value, nil
 }
 
+func daemonConfigSetMAAS(d *Daemon, key string, value string) (string, error) {
+       maasUrl := daemonConfig["maas.api.url"].Get()
+       if key == "maas.api.url" {
+               maasUrl = value
+       }
+
+       maasKey := daemonConfig["maas.api.key"].Get()
+       if key == "maas.api.key" {
+               maasKey = value
+       }
+
+       maasMachine := daemonConfig["maas.machine"].Get()
+       if key == "maas.machine" {
+               maasMachine = value
+       }
+
+       err := d.setupMAASController(maasUrl, maasKey, maasMachine)
+       if err != nil {
+               return "", err
+       }
+
+       return value, nil
+}
+
 func daemonConfigTriggerExpiry(d *Daemon, key string, value string) {
        // Trigger an image pruning run
        d.taskPruneImages.Reset()
diff --git a/lxd/state/state.go b/lxd/state/state.go
index 62b0afd72..aad9d22b5 100644
--- a/lxd/state/state.go
+++ b/lxd/state/state.go
@@ -2,6 +2,7 @@ package state
 
 import (
        "github.com/lxc/lxd/lxd/db"
+       "github.com/lxc/lxd/lxd/maas"
        "github.com/lxc/lxd/lxd/sys"
 )
 
@@ -9,15 +10,17 @@ import (
 // and the operating system. It's typically used by model entities such as
 // containers, volumes, etc. in order to perform changes.
 type State struct {
-       DB *db.Node
-       OS *sys.OS
+       DB   *db.Node
+       MAAS *maas.Controller
+       OS   *sys.OS
 }
 
 // NewState returns a new State object with the given database and operating
 // system components.
-func NewState(db *db.Node, os *sys.OS) *State {
+func NewState(db *db.Node, maas *maas.Controller, os *sys.OS) *State {
        return &State{
-               DB: db,
-               OS: os,
+               DB:   db,
+               MAAS: maas,
+               OS:   os,
        }
 }

From 36bb549421eefb6c89c4a001be72c1c51bd9509b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <[email protected]>
Date: Mon, 18 Dec 2017 23:21:09 -0500
Subject: [PATCH 4/4] lxd/containers: Integrate MAAS with containers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <[email protected]>
---
 lxd/container_lxc.go | 180 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 180 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index e2a89b9c1..bed1fb40f 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -25,6 +25,7 @@ import (
        "gopkg.in/yaml.v2"
 
        "github.com/lxc/lxd/lxd/db"
+       "github.com/lxc/lxd/lxd/maas"
        "github.com/lxc/lxd/lxd/state"
        "github.com/lxc/lxd/lxd/types"
        "github.com/lxc/lxd/lxd/util"
@@ -430,6 +431,14 @@ func containerLXCCreate(s *state.State, args 
db.ContainerArgs) (container, error
                return nil, err
        }
 
+       // Update MAAS
+       err = c.maasUpdate(false)
+       if err != nil {
+               c.Delete()
+               logger.Error("Failed creating container", ctxMap)
+               return nil, err
+       }
+
        // Update lease files
        networkUpdateStatic(s, "")
 
@@ -3093,6 +3102,13 @@ func (c *containerLXC) Delete() error {
                                }
                        }
                }
+
+               // Delete the MAAS entry
+               err = c.maasDelete()
+               if err != nil {
+                       logger.Error("Failed deleting container MAAS record", 
log.Ctx{"name": c.Name(), "err": err})
+                       return err
+               }
        }
 
        // Remove the database record
@@ -3163,6 +3179,12 @@ func (c *containerLXC) Rename(newName string) error {
        // Clean things up
        c.cleanup()
 
+       // Rename the MAAS entry
+       err = c.maasRename(newName)
+       if err != nil {
+               return err
+       }
+
        // Rename the logging path
        os.RemoveAll(shared.LogPath(newName))
        if shared.PathExists(c.LogPath()) {
@@ -3719,6 +3741,22 @@ func (c *containerLXC) Update(args db.ContainerArgs, 
userRequested bool) error {
                }
        }
 
+       // Update MAAS
+       updateMAAS := false
+       for _, key := range []string{"maas.subnet.ipv4", "maas.subnet.ipv6", 
"ipv4.address", "ipv6.address"} {
+               if shared.StringInSlice(key, updateDiff) {
+                       updateMAAS = true
+                       break
+               }
+       }
+
+       if updateMAAS {
+               err = c.maasUpdate(true)
+               if err != nil {
+                       return err
+               }
+       }
+
        // Apply the live changes
        if isRunning {
                // Live update the container config
@@ -7730,3 +7768,145 @@ func (c *containerLXC) StoragePool() (string, error) {
 
        return poolName, nil
 }
+
+func (c *containerLXC) maasInterfaces() ([]maas.ContainerInterface, error) {
+       interfaces := []maas.ContainerInterface{}
+       for k, m := range c.expandedDevices {
+               if m["type"] != "nic" {
+                       continue
+               }
+
+               if m["maas.subnet.ipv4"] == "" && m["maas.subnet.ipv6"] == "" {
+                       continue
+               }
+
+               m, err := c.fillNetworkDevice(k, m)
+               if err != nil {
+                       return nil, err
+               }
+
+               subnets := []maas.ContainerInterfaceSubnet{}
+
+               // IPv4
+               if m["maas.subnet.ipv4"] != "" {
+                       subnet := maas.ContainerInterfaceSubnet{
+                               Name:    m["maas.subnet.ipv4"],
+                               Address: m["ipv4.address"],
+                       }
+
+                       subnets = append(subnets, subnet)
+               }
+
+               // IPv6
+               if m["maas.subnet.ipv6"] != "" {
+                       subnet := maas.ContainerInterfaceSubnet{
+                               Name:    m["maas.subnet.ipv6"],
+                               Address: m["ipv6.address"],
+                       }
+
+                       subnets = append(subnets, subnet)
+               }
+
+               iface := maas.ContainerInterface{
+                       Name:       m["name"],
+                       MACAddress: m["hwaddr"],
+                       Subnets:    subnets,
+               }
+
+               interfaces = append(interfaces, iface)
+       }
+
+       return interfaces, nil
+}
+
+func (c *containerLXC) maasConnected() bool {
+       for _, m := range c.expandedDevices {
+               if m["type"] != "nic" {
+                       continue
+               }
+
+               if m["maas.subnet.ipv4"] != "" || m["maas.subnet.ipv6"] != "" {
+                       return true
+               }
+       }
+
+       return false
+}
+
+func (c *containerLXC) maasUpdate(force bool) error {
+       if c.state.MAAS == nil {
+               return nil
+       }
+
+       if !c.maasConnected() {
+               if force {
+                       exists, err := c.state.MAAS.DefinedContainer(c.name)
+                       if err != nil {
+                               return err
+                       }
+
+                       if exists {
+                               return c.state.MAAS.DeleteContainer(c.name)
+                       }
+               }
+               return nil
+       }
+
+       interfaces, err := c.maasInterfaces()
+       if err != nil {
+               return err
+       }
+
+       exists, err := c.state.MAAS.DefinedContainer(c.name)
+       if err != nil {
+               return err
+       }
+
+       if exists {
+               return c.state.MAAS.UpdateContainer(c.name, interfaces)
+       }
+
+       return c.state.MAAS.CreateContainer(c.name, interfaces)
+}
+
+func (c *containerLXC) maasRename(newName string) error {
+       if c.state.MAAS == nil {
+               return nil
+       }
+
+       if !c.maasConnected() {
+               return nil
+       }
+
+       exists, err := c.state.MAAS.DefinedContainer(c.name)
+       if err != nil {
+               return err
+       }
+
+       if !exists {
+               return c.maasUpdate(false)
+       }
+
+       return c.state.MAAS.RenameContainer(c.name, newName)
+}
+
+func (c *containerLXC) maasDelete() error {
+       if c.state.MAAS == nil {
+               return nil
+       }
+
+       if !c.maasConnected() {
+               return nil
+       }
+
+       exists, err := c.state.MAAS.DefinedContainer(c.name)
+       if err != nil {
+               return err
+       }
+
+       if !exists {
+               return nil
+       }
+
+       return c.state.MAAS.DeleteContainer(c.name)
+}
_______________________________________________
lxc-devel mailing list
[email protected]
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to