[lxc-devel] [lxd/master] Add MAAS integration
stgraber on Github
lxc-bot at linuxcontainers.org
Tue Dec 19 04:28:11 UTC 2017
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20171219/e6aa3d38/attachment.bin>
-------------- next part --------------
From 42e329a09c67f9d8a10b621ba13d79993c1de608 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
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 <stgraber at ubuntu.com>
---
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?= <stgraber at ubuntu.com>
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 <stgraber at ubuntu.com>
---
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?= <stgraber at ubuntu.com>
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 <stgraber at ubuntu.com>
---
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?= <stgraber at ubuntu.com>
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 <stgraber at ubuntu.com>
---
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)
+}
More information about the lxc-devel
mailing list