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
