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

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 b407d52b250282f92ace201c01979aa39c84bbfb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Mon, 13 Feb 2017 21:18:23 -0500
Subject: [PATCH 01/11] tests: Fix typo
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 test/main.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/main.sh b/test/main.sh
index 66585ca..f6aad33 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -455,7 +455,7 @@ fi
 run_test test_check_deps "checking dependencies"
 run_test test_static_analysis "static analysis"
 run_test test_database_update "database schema updates"
-run_test test_remote_url "remote  url handling"
+run_test test_remote_url "remote url handling"
 run_test test_remote_admin "remote administration"
 run_test test_remote_usage "remote usage"
 run_test test_basic_usage "basic usage"

From fa35a9d9eaa0fb45f67b23636bb244902ce7df82 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 14 Feb 2017 11:27:48 -0500
Subject: [PATCH 02/11] Don't include spaces in translated strings
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxc/info.go |  8 ++++----
 po/lxd.pot  | 34 +++++++++++++++++-----------------
 2 files changed, 21 insertions(+), 21 deletions(-)

diff --git a/lxc/info.go b/lxc/info.go
index 2300ce0..31f46f2 100644
--- a/lxc/info.go
+++ b/lxc/info.go
@@ -140,7 +140,7 @@ func (c *infoCmd) containerInfo(d *lxd.Client, name string, 
showLog bool) error
                }
 
                if diskInfo != "" {
-                       fmt.Println(i18n.G("  Disk usage:"))
+                       fmt.Println(fmt.Sprintf("  %s", i18n.G("Disk usage:")))
                        fmt.Printf(diskInfo)
                }
 
@@ -151,7 +151,7 @@ func (c *infoCmd) containerInfo(d *lxd.Client, name string, 
showLog bool) error
                }
 
                if cpuInfo != "" {
-                       fmt.Println(i18n.G("  CPU usage:"))
+                       fmt.Println(fmt.Sprintf("  %s", i18n.G("CPU usage:")))
                        fmt.Printf(cpuInfo)
                }
 
@@ -174,7 +174,7 @@ func (c *infoCmd) containerInfo(d *lxd.Client, name string, 
showLog bool) error
                }
 
                if memoryInfo != "" {
-                       fmt.Println(i18n.G("  Memory usage:"))
+                       fmt.Println(fmt.Sprintf("  %s", i18n.G("Memory 
usage:")))
                        fmt.Printf(memoryInfo)
                }
 
@@ -191,7 +191,7 @@ func (c *infoCmd) containerInfo(d *lxd.Client, name string, 
showLog bool) error
                }
 
                if networkInfo != "" {
-                       fmt.Println(i18n.G("  Network usage:"))
+                       fmt.Println(fmt.Sprintf("  %s", i18n.G("Network 
usage:")))
                        fmt.Printf(networkInfo)
                }
        }
diff --git a/po/lxd.pot b/po/lxd.pot
index e735b8f..f8106af 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel@lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2017-02-14 01:13+0100\n"
+        "POT-Creation-Date: 2017-02-14 16:58-0500\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
         "Language-Team: LANGUAGE <l...@li.org>\n"
@@ -16,22 +16,6 @@ msgstr  "Project-Id-Version: lxd\n"
         "Content-Type: text/plain; charset=CHARSET\n"
         "Content-Transfer-Encoding: 8bit\n"
 
-#: lxc/info.go:154
-msgid   "  CPU usage:"
-msgstr  ""
-
-#: lxc/info.go:143
-msgid   "  Disk usage:"
-msgstr  ""
-
-#: lxc/info.go:177
-msgid   "  Memory usage:"
-msgstr  ""
-
-#: lxc/info.go:194
-msgid   "  Network usage:"
-msgstr  ""
-
 #: lxc/storage.go:33
 msgid   "### This is a yaml representation of a storage pool.\n"
         "### Any line starting with a '# will be ignored.\n"
@@ -197,6 +181,10 @@ msgstr  ""
 msgid   "CPU usage (in seconds)"
 msgstr  ""
 
+#: lxc/info.go:154
+msgid   "CPU usage:"
+msgstr  ""
+
 #: lxc/list.go:429
 msgid   "CREATED AT"
 msgstr  ""
@@ -356,6 +344,10 @@ msgstr  ""
 msgid   "Device %s removed from %s"
 msgstr  ""
 
+#: lxc/info.go:143
+msgid   "Disk usage:"
+msgstr  ""
+
 #: lxc/list.go:576
 msgid   "EPHEMERAL"
 msgstr  ""
@@ -877,6 +869,10 @@ msgstr  ""
 msgid   "Memory (peak)"
 msgstr  ""
 
+#: lxc/info.go:177
+msgid   "Memory usage:"
+msgstr  ""
+
 #: lxc/help.go:87
 msgid   "Missing summary."
 msgstr  ""
@@ -947,6 +943,10 @@ msgstr  ""
 msgid   "Network name"
 msgstr  ""
 
+#: lxc/info.go:194
+msgid   "Network usage:"
+msgstr  ""
+
 #: lxc/image.go:168 lxc/publish.go:35
 msgid   "New alias to define at target"
 msgstr  ""

From b209a944e950b6fb72ae7aa755aa711d8b6a8bd5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 14 Feb 2017 11:41:34 -0500
Subject: [PATCH 03/11] tests: Add golint
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 Makefile                       | 1 +
 test/suites/static_analysis.sh | 9 ++++++---
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/Makefile b/Makefile
index 3970a11..5d092ed 100644
--- a/Makefile
+++ b/Makefile
@@ -48,6 +48,7 @@ protobuf:
 check: default
        go get -v -x github.com/rogpeppe/godeps
        go get -v -x github.com/remyoudompheng/go-misc/deadcode
+       go get -v -x github.com/golang/lint/golint
        go test -v $(TAGS) $(DEBUG) ./...
        cd test && ./main.sh
 
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index 878bce7..f17ade4 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -25,9 +25,7 @@ test_static_analysis() {
     fi
 
     ## go vet, if it exists
-    have_go_vet=1
-    go help vet > /dev/null 2>&1 || have_go_vet=0
-    if [ "${have_go_vet}" -eq 1 ]; then
+    if go help vet >/dev/null 2>&1; then
       go vet ./...
     fi
 
@@ -36,6 +34,11 @@ test_static_analysis() {
       vet --all .
     fi
 
+    ## golint
+    if which golint >/dev/null 2>&1; then
+      golint -set_exit_status shared/api/
+    fi
+
     ## deadcode
     if which deadcode >/dev/null 2>&1; then
       for path in . fuidshift lxc lxd lxd/types shared shared/api shared/i18n 
shared/ioprogress shared/logging shared/osarch shared/simplestreams 
shared/termios shared/version test/lxd-benchmark; do

From cbbed2577ed8b19c2345a0132650d55b1a8812c9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 14 Feb 2017 14:27:22 -0500
Subject: [PATCH 04/11] Use a tmpfs for shmounts
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Avoids some information leakage from the host.

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/daemon.go  | 21 +++++++--------------
 shared/util.go | 36 ------------------------------------
 2 files changed, 7 insertions(+), 50 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 4509703..f37bdb0 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -402,35 +402,28 @@ var sharedMounted bool
 var sharedMountsLock sync.Mutex
 
 func setupSharedMounts() error {
+       // Check if we already went through this
        if sharedMounted {
                return nil
        }
 
+       // Get a lock to prevent races
        sharedMountsLock.Lock()
        defer sharedMountsLock.Unlock()
 
-       if sharedMounted {
-               return nil
-       }
-
+       // Check if already setup
        path := shared.VarPath("shmounts")
-
-       isShared, err := shared.IsOnSharedMount(path)
-       if err != nil {
-               return err
-       }
-
-       if isShared {
-               // / may already be ms-shared, or shmounts may have
-               // been mounted by a previous lxd run
+       if shared.IsMountPoint(path) {
                sharedMounted = true
                return nil
        }
 
-       if err := syscall.Mount(path, path, "none", syscall.MS_BIND, ""); err 
!= nil {
+       // Mount a new tmpfs
+       if err := syscall.Mount("tmpfs", path, "tmpfs", 0, 
"size=100k,mode=0711"); err != nil {
                return err
        }
 
+       // Mark as MS_SHARED and MS_REC
        var flags uintptr = syscall.MS_SHARED | syscall.MS_REC
        if err := syscall.Mount(path, path, "none", flags, ""); err != nil {
                return err
diff --git a/shared/util.go b/shared/util.go
index 0474e4d..69f83ac 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -430,42 +430,6 @@ func IsTrue(value string) bool {
        return false
 }
 
-func IsOnSharedMount(pathName string) (bool, error) {
-       file, err := os.Open("/proc/self/mountinfo")
-       if err != nil {
-               return false, err
-       }
-       defer file.Close()
-
-       absPath, err := filepath.Abs(pathName)
-       if err != nil {
-               return false, err
-       }
-
-       expPath, err := os.Readlink(absPath)
-       if err != nil {
-               expPath = absPath
-       }
-
-       scanner := bufio.NewScanner(file)
-       for scanner.Scan() {
-               line := scanner.Text()
-               rows := strings.Fields(line)
-
-               if rows[4] != expPath {
-                       continue
-               }
-
-               if strings.HasPrefix(rows[6], "shared:") {
-                       return true, nil
-               } else {
-                       return false, nil
-               }
-       }
-
-       return false, nil
-}
-
 func IsBlockdev(fm os.FileMode) bool {
        return ((fm&os.ModeDevice != 0) && (fm&os.ModeCharDevice == 0))
 }

From 17a4ad1a240a78fd82013101466311f5080af4f0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 14 Feb 2017 15:01:10 -0500
Subject: [PATCH 05/11] Mount a tmpfs under devlxd
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2877

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/daemon.go | 26 +++++++++++++++++++-------
 1 file changed, 19 insertions(+), 7 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index f37bdb0..2de8487 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -854,6 +854,17 @@ func (d *Daemon) Init() error {
                daemonConfig["core.proxy_ignore_hosts"].Get(),
        )
 
+       /* Setup some mounts (nice to have) */
+       if !d.MockMode {
+               // Attempt to mount the shmounts tmpfs
+               setupSharedMounts()
+
+               // Attempt to Mount the devlxd tmpfs
+               if !shared.IsMountPoint(shared.VarPath("devlxd")) {
+                       syscall.Mount("tmpfs", shared.VarPath("devlxd"), 
"tmpfs", 0, "size=100k,mode=0755")
+               }
+       }
+
        /* Setup /dev/lxd */
        shared.LogInfof("Starting /dev/lxd handler")
        d.devlxd, err = createAndBindDevLxd()
@@ -1161,23 +1172,24 @@ func (d *Daemon) Stop() error {
                }
        }
 
+       shared.LogInfof("Stopping /dev/lxd handler")
+       d.devlxd.Close()
+       shared.LogInfof("Stopped /dev/lxd handler")
+
        if n, err := d.numRunningContainers(); err != nil || n == 0 {
-               shared.LogInfof("Unmounting shmounts")
+               shared.LogInfof("Unmounting temporary filesystems")
 
+               syscall.Unmount(shared.VarPath("devlxd"), syscall.MNT_DETACH)
                syscall.Unmount(shared.VarPath("shmounts"), syscall.MNT_DETACH)
 
-               shared.LogInfof("Done unmounting shmounts")
+               shared.LogInfof("Done unmounting temporary filesystems")
        } else {
-               shared.LogDebugf("Not unmounting shmounts (containers are still 
running)")
+               shared.LogDebugf("Not unmounting temporary filesystems 
(containers are still running)")
        }
 
        shared.LogInfof("Closing the database")
        d.db.Close()
 
-       shared.LogInfof("Stopping /dev/lxd handler")
-       d.devlxd.Close()
-       shared.LogInfof("Stopped /dev/lxd handler")
-
        shared.LogInfof("Saving simplestreams cache")
        imageSaveStreamCache()
        shared.LogInfof("Saved simplestreams cache")

From c77f2052231fc4b775df742289b644e2234f23f6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 14 Feb 2017 15:27:03 -0500
Subject: [PATCH 06/11] Allow setting network interface name on attach
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2873

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxc/network.go | 16 ++++++++++++----
 po/lxd.pot     | 38 +++++++++++++++++++-------------------
 2 files changed, 31 insertions(+), 23 deletions(-)

diff --git a/lxc/network.go b/lxc/network.go
index 0966173..9e890d7 100644
--- a/lxc/network.go
+++ b/lxc/network.go
@@ -61,8 +61,8 @@ lxc network edit [<remote>:]<network>
     Example: lxc network edit <network> # launch editor
              cat network.yaml | lxc network edit <network> # read from 
network.yaml
 
-lxc network attach [<remote>:]<network> <container> [device name]
-lxc network attach-profile [<remote>:]<network> <profile> [device name]
+lxc network attach [<remote>:]<network> <container> [device name] [interface 
name]
+lxc network attach-profile [<remote>:]<network> <profile> [device name] 
[interface name]
 
 lxc network detach [<remote>:]<network> <container> [device name]
 lxc network detach-profile [<remote>:]<network> <container> [device name]`)
@@ -118,7 +118,7 @@ func (c *networkCmd) run(config *lxd.Config, args []string) 
error {
 }
 
 func (c *networkCmd) doNetworkAttach(client *lxd.Client, name string, args 
[]string) error {
-       if len(args) < 1 || len(args) > 2 {
+       if len(args) < 1 || len(args) > 3 {
                return errArgs
        }
 
@@ -139,6 +139,10 @@ func (c *networkCmd) doNetworkAttach(client *lxd.Client, 
name string, args []str
        }
 
        props := []string{fmt.Sprintf("nictype=%s", nicType), 
fmt.Sprintf("parent=%s", name)}
+       if len(args) > 2 {
+               props = append(props, fmt.Sprintf("name=%s", args[2]))
+       }
+
        resp, err := client.ContainerDeviceAdd(container, devName, "nic", props)
        if err != nil {
                return err
@@ -148,7 +152,7 @@ func (c *networkCmd) doNetworkAttach(client *lxd.Client, 
name string, args []str
 }
 
 func (c *networkCmd) doNetworkAttachProfile(client *lxd.Client, name string, 
args []string) error {
-       if len(args) < 1 || len(args) > 2 {
+       if len(args) < 1 || len(args) > 3 {
                return errArgs
        }
 
@@ -169,6 +173,10 @@ func (c *networkCmd) doNetworkAttachProfile(client 
*lxd.Client, name string, arg
        }
 
        props := []string{fmt.Sprintf("nictype=%s", nicType), 
fmt.Sprintf("parent=%s", name)}
+       if len(args) > 2 {
+               props = append(props, fmt.Sprintf("name=%s", args[2]))
+       }
+
        _, err = client.ProfileDeviceAdd(profile, devName, "nic", props)
        return err
 }
diff --git a/po/lxd.pot b/po/lxd.pot
index f8106af..04e1782 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -189,7 +189,7 @@ msgstr  ""
 msgid   "CREATED AT"
 msgstr  ""
 
-#: lxc/config.go:114 lxc/network.go:463
+#: lxc/config.go:114 lxc/network.go:471
 #, c-format
 msgid   "Can't read from stdin: %s"
 msgstr  ""
@@ -199,7 +199,7 @@ msgstr  ""
 msgid   "Can't unset key '%s', it's not currently set."
 msgstr  ""
 
-#: lxc/network.go:390 lxc/profile.go:424 lxc/storage.go:522
+#: lxc/network.go:398 lxc/profile.go:424 lxc/storage.go:522
 msgid   "Cannot provide container name to list"
 msgstr  ""
 
@@ -233,7 +233,7 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:535 lxc/config.go:600 lxc/image.go:737 lxc/network.go:346 
lxc/profile.go:218 lxc/storage.go:478 lxc/storage.go:824
+#: lxc/config.go:535 lxc/config.go:600 lxc/image.go:737 lxc/network.go:354 
lxc/profile.go:218 lxc/storage.go:478 lxc/storage.go:824
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -617,7 +617,7 @@ msgstr  ""
 msgid   "Log:"
 msgstr  ""
 
-#: lxc/network.go:428
+#: lxc/network.go:436
 msgid   "MANAGED"
 msgstr  ""
 
@@ -739,8 +739,8 @@ msgid   "Manage networks.\n"
         "    Example: lxc network edit <network> # launch editor\n"
         "             cat network.yaml | lxc network edit <network> # read 
from network.yaml\n"
         "\n"
-        "lxc network attach [<remote>:]<network> <container> [device name]\n"
-        "lxc network attach-profile [<remote>:]<network> <profile> [device 
name]\n"
+        "lxc network attach [<remote>:]<network> <container> [device name] 
[interface name]\n"
+        "lxc network attach-profile [<remote>:]<network> <profile> [device 
name] [interface name]\n"
         "\n"
         "lxc network detach [<remote>:]<network> <container> [device name]\n"
         "lxc network detach-profile [<remote>:]<network> <container> [device 
name]"
@@ -891,7 +891,7 @@ msgid   "Monitor activity on the LXD server.\n"
         "    lxc monitor --type=logging"
 msgstr  ""
 
-#: lxc/network.go:216 lxc/network.go:265 lxc/storage.go:309 lxc/storage.go:405
+#: lxc/network.go:224 lxc/network.go:273 lxc/storage.go:309 lxc/storage.go:405
 msgid   "More than one device matches, specify the device name."
 msgstr  ""
 
@@ -916,11 +916,11 @@ msgstr  ""
 msgid   "Must supply container name for: "
 msgstr  ""
 
-#: lxc/list.go:431 lxc/network.go:426 lxc/profile.go:451 lxc/remote.go:380 
lxc/storage.go:550 lxc/storage.go:640 lxc/storage.go:673
+#: lxc/list.go:431 lxc/network.go:434 lxc/profile.go:451 lxc/remote.go:380 
lxc/storage.go:550 lxc/storage.go:640 lxc/storage.go:673
 msgid   "NAME"
 msgstr  ""
 
-#: lxc/network.go:412 lxc/remote.go:354 lxc/remote.go:359
+#: lxc/network.go:420 lxc/remote.go:354 lxc/remote.go:359
 msgid   "NO"
 msgstr  ""
 
@@ -929,12 +929,12 @@ msgstr  ""
 msgid   "Name: %s"
 msgstr  ""
 
-#: lxc/network.go:190
+#: lxc/network.go:198
 #, c-format
 msgid   "Network %s created"
 msgstr  ""
 
-#: lxc/network.go:293
+#: lxc/network.go:301
 #, c-format
 msgid   "Network %s deleted"
 msgstr  ""
@@ -955,7 +955,7 @@ msgstr  ""
 msgid   "No certificate provided to add"
 msgstr  ""
 
-#: lxc/network.go:225 lxc/network.go:274
+#: lxc/network.go:233 lxc/network.go:282
 msgid   "No device found for this network"
 msgstr  ""
 
@@ -975,7 +975,7 @@ msgstr  ""
 msgid   "Only https:// is supported for remote image import."
 msgstr  ""
 
-#: lxc/network.go:322 lxc/network.go:449
+#: lxc/network.go:330 lxc/network.go:457
 msgid   "Only managed networks can be modified."
 msgstr  ""
 
@@ -1037,7 +1037,7 @@ msgstr  ""
 msgid   "Pid: %d"
 msgstr  ""
 
-#: lxc/network.go:347 lxc/profile.go:219 lxc/storage.go:479 lxc/storage.go:825
+#: lxc/network.go:355 lxc/profile.go:219 lxc/storage.go:479 lxc/storage.go:825
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
@@ -1303,7 +1303,7 @@ msgstr  ""
 msgid   "Swap (peak)"
 msgstr  ""
 
-#: lxc/list.go:436 lxc/network.go:427 lxc/storage.go:641 lxc/storage.go:674
+#: lxc/list.go:436 lxc/network.go:435 lxc/storage.go:641 lxc/storage.go:674
 msgid   "TYPE"
 msgstr  ""
 
@@ -1332,11 +1332,11 @@ msgstr  ""
 msgid   "The opposite of `lxc pause` is `lxc start`."
 msgstr  ""
 
-#: lxc/network.go:230 lxc/network.go:279 lxc/storage.go:323 lxc/storage.go:419
+#: lxc/network.go:238 lxc/network.go:287 lxc/storage.go:323 lxc/storage.go:419
 msgid   "The specified device doesn't exist"
 msgstr  ""
 
-#: lxc/network.go:234 lxc/network.go:283
+#: lxc/network.go:242 lxc/network.go:291
 msgid   "The specified device doesn't match the network"
 msgstr  ""
 
@@ -1390,7 +1390,7 @@ msgstr  ""
 msgid   "URL"
 msgstr  ""
 
-#: lxc/network.go:429 lxc/profile.go:452 lxc/storage.go:553 lxc/storage.go:642 
lxc/storage.go:675
+#: lxc/network.go:437 lxc/profile.go:452 lxc/storage.go:553 lxc/storage.go:642 
lxc/storage.go:675
 msgid   "USED BY"
 msgstr  ""
 
@@ -1424,7 +1424,7 @@ msgstr  ""
 msgid   "Whether or not to snapshot the container's running state"
 msgstr  ""
 
-#: lxc/network.go:414 lxc/remote.go:356 lxc/remote.go:361
+#: lxc/network.go:422 lxc/remote.go:356 lxc/remote.go:361
 msgid   "YES"
 msgstr  ""
 

From 59bbab88ec8c1f2a4c7772eba5640a72c9f7bc4b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 14 Feb 2017 16:08:46 -0500
Subject: [PATCH 07/11] Fix error handling on FileRemove
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/container_lxc.go | 23 ++++++++++++++++++-----
 1 file changed, 18 insertions(+), 5 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 05f252f..46600bf 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -4597,6 +4597,8 @@ func (c *containerLXC) FilePush(srcpath string, dstpath 
string, uid int, gid int
 }
 
 func (c *containerLXC) FileRemove(path string) error {
+       var errStr string
+
        // Setup container storage if needed
        if !c.IsRunning() {
                err := c.StorageStart()
@@ -4623,13 +4625,24 @@ func (c *containerLXC) FileRemove(path string) error {
        }
 
        // Process forkremovefile response
-       if string(out) != "" {
-               if strings.HasPrefix(string(out), "error:") {
-                       return 
fmt.Errorf(strings.TrimPrefix(strings.TrimSuffix(string(out), "\n"), "error: "))
+       for _, line := range strings.Split(strings.TrimRight(string(out), 
"\n"), "\n") {
+               if line == "" {
+                       continue
                }
 
-               for _, line := range 
strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
-                       shared.LogDebugf("forkremovefile: %s", line)
+               // Extract errors
+               if strings.HasPrefix(line, "error: ") {
+                       errStr = strings.TrimPrefix(line, "error: ")
+                       continue
+               }
+
+               if strings.HasPrefix(line, "errno: ") {
+                       errno := strings.TrimPrefix(line, "errno: ")
+                       if errno == "2" {
+                               return os.ErrNotExist
+                       }
+
+                       return fmt.Errorf(errStr)
                }
        }
 

From b9d73d05bfc439c6e740fd18cb77554b499f54dc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 14 Feb 2017 16:09:03 -0500
Subject: [PATCH 08/11] Implement file DELETE
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2868

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 client.go             | 14 ++++++++++++++
 doc/api-extensions.md |  3 +++
 doc/rest-api.md       | 12 ++++++++++++
 lxc/file.go           | 29 +++++++++++++++++++++++++++++
 lxd/api_1.0.go        |  1 +
 lxd/container_file.go | 11 +++++++++++
 lxd/containers.go     |  7 ++++---
 7 files changed, 74 insertions(+), 3 deletions(-)

diff --git a/client.go b/client.go
index e013499..7232b7b 100644
--- a/client.go
+++ b/client.go
@@ -1962,6 +1962,20 @@ func (c *Client) RecursivePullFile(container string, p 
string, targetDir string)
        return nil
 }
 
+func (c *Client) DeleteFile(container string, p string) error {
+       if c.Remote.Public {
+               return fmt.Errorf("This function isn't supported by public 
remotes.")
+       }
+
+       query := url.Values{"path": []string{p}}
+       _, err := c.delete(fmt.Sprintf("containers/%s/files?%s", container, 
query.Encode()), nil, api.SyncResponse)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
 func (c *Client) GetMigrationSourceWS(container string) (*api.Response, error) 
{
        if c.Remote.Public {
                return nil, fmt.Errorf("This function isn't supported by public 
remotes.")
diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index 55a91d3..b6a9d23 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -202,3 +202,6 @@ This includes:
 * DELETE /1.0/storage-pools/<pool>/volumes/<volume_type>/<name> (see 
rest-api.md for details)
 
 - All storage configuration options (see configuration.md for details)
+
+## file\_delete
+Implements DELETE in /1.0/containers/\<name\>/files
diff --git a/doc/rest-api.md b/doc/rest-api.md
index 65302a1..8a07bef 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -819,6 +819,18 @@ The following headers may be set by the client:
 This is designed to be easily usable from the command line or even a web
 browser.
 
+### DELETE (?path=/path/inside/the/container)
+ * Description: delete a file in the container
+ * Introduced: with API extension "file\_delete"
+ * Authentication: trusted
+ * Operation: sync
+ * Return: standard return value or standard error
+
+Input (none at present):
+
+    {
+    }
+
 ## /1.0/containers/\<name\>/snapshots
 ### GET
  * Description: List of snapshots
diff --git a/lxc/file.go b/lxc/file.go
index 1fbb65c..9760bee 100644
--- a/lxc/file.go
+++ b/lxc/file.go
@@ -38,6 +38,7 @@ func (c *fileCmd) usage() string {
 
 lxc file pull [-r|--recursive] [<remote>:]<container> 
[[<remote>:]<container>...] <target path>
 lxc file push [-r|--recursive] [-p|--create-dirs] [--uid=UID] [--gid=GID] 
[--mode=MODE] <source path> [<source path>...] [<remote>:]<container>
+lxc file delete [<remote>:]<container> [[<remote>:]<container>...]
 lxc file edit [<remote>:]<container>/<path>
 
 <source> in the case of pull, <target> in the case of push and <file> in the 
case of edit are <container name>/<path>
@@ -340,6 +341,32 @@ func (c *fileCmd) pull(config *lxd.Config, args []string) 
error {
        return nil
 }
 
+func (c *fileCmd) delete(config *lxd.Config, args []string) error {
+       if len(args) < 1 {
+               return errArgs
+       }
+
+       for _, f := range args[:len(args)] {
+               pathSpec := strings.SplitN(f, "/", 2)
+               if len(pathSpec) != 2 {
+                       return fmt.Errorf(i18n.G("Invalid path %s"), f)
+               }
+
+               remote, container := config.ParseRemoteAndContainer(pathSpec[0])
+               d, err := lxd.NewClient(config, remote)
+               if err != nil {
+                       return err
+               }
+
+               err = d.DeleteFile(container, pathSpec[1])
+               if err != nil {
+                       return err
+               }
+       }
+
+       return nil
+}
+
 func (c *fileCmd) edit(config *lxd.Config, args []string) error {
        if len(args) != 1 {
                return errArgs
@@ -390,6 +417,8 @@ func (c *fileCmd) run(config *lxd.Config, args []string) 
error {
                return c.push(config, true, args[1:])
        case "pull":
                return c.pull(config, args[1:])
+       case "delete":
+               return c.delete(config, args[1:])
        case "edit":
                return c.edit(config, args[1:])
        default:
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 3951f94..963c815 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -105,6 +105,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
                        "network_firewall_filtering",
                        "network_routes",
                        "storage",
+                       "file_delete",
                },
                APIStatus:  "stable",
                APIVersion: version.APIVersion,
diff --git a/lxd/container_file.go b/lxd/container_file.go
index 7e9d20b..a4c1714 100644
--- a/lxd/container_file.go
+++ b/lxd/container_file.go
@@ -30,6 +30,8 @@ func containerFileHandler(d *Daemon, r *http.Request) 
Response {
                return containerFileGet(c, path, r)
        case "POST":
                return containerFilePut(c, path, r)
+       case "DELETE":
+               return containerFileDelete(c, path, r)
        default:
                return NotFound
        }
@@ -117,3 +119,12 @@ func containerFilePut(c container, path string, r 
*http.Request) Response {
                return InternalError(fmt.Errorf("bad file type %s", type_))
        }
 }
+
+func containerFileDelete(c container, path string, r *http.Request) Response {
+       err := c.FileRemove(path)
+       if err != nil {
+               return SmartError(err)
+       }
+
+       return EmptySyncResponse
+}
diff --git a/lxd/containers.go b/lxd/containers.go
index d5305d3..3bb06ae 100644
--- a/lxd/containers.go
+++ b/lxd/containers.go
@@ -33,9 +33,10 @@ var containerStateCmd = Command{
 }
 
 var containerFileCmd = Command{
-       name: "containers/{name}/files",
-       get:  containerFileHandler,
-       post: containerFileHandler,
+       name:   "containers/{name}/files",
+       get:    containerFileHandler,
+       post:   containerFileHandler,
+       delete: containerFileHandler,
 }
 
 var containerSnapshotsCmd = Command{

From a3b28399a72627a29e118a128af798a67987ef7d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 14 Feb 2017 16:15:05 -0500
Subject: [PATCH 09/11] doc: Clarify PUT vs PATCH
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2873

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 doc/rest-api.md | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/doc/rest-api.md b/doc/rest-api.md
index 8a07bef..4c3f5e0 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -161,6 +161,21 @@ It's recommended that the client always subscribes to the 
operations
 notification type before triggering remote operations so that it doesn't
 have to then poll for their status.
 
+# PUT vs PATCH
+The LXD API supports both PUT and PATCH to modify existing objects.
+
+PUT replaces the entire object with a new definition, it's typically
+called after the current object state was retrieved through GET.
+
+To avoid race conditions, the Etag header should be read from the GET
+response and sent as If-Match for the PUT request. This will cause LXD
+to fail the request if the object was modified between GET and PUT.
+
+PATCH can be used to modify a single field inside an object by only
+specifying the property that you want to change. To unset a key, setting
+it to empty will usually do the trick, but there are cases where PATCH
+won't work and PUT needs to be used instead.
+
 # API structure
  * /
    * /1.0

From e33dc0715dc335d8dd8bc246a687c4abbe50e1cb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 14 Feb 2017 16:36:47 -0500
Subject: [PATCH 10/11] Make it possible to append to a file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2871

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 client.go             |  2 +-
 doc/api-extensions.md |  3 +++
 doc/rest-api.md       |  1 +
 lxd/api_1.0.go        |  1 +
 lxd/container.go      |  2 +-
 lxd/container_file.go | 12 ++++++++----
 lxd/container_lxc.go  |  6 ++++--
 lxd/main_nsexec.go    | 25 ++++++++++++++++++-------
 shared/util.go        | 12 ++++++++++--
 9 files changed, 47 insertions(+), 17 deletions(-)

diff --git a/client.go b/client.go
index 7232b7b..6893165 100644
--- a/client.go
+++ b/client.go
@@ -1896,7 +1896,7 @@ func (c *Client) PullFile(container string, p string) 
(int, int, int, string, io
                return 0, 0, 0, "", nil, nil, err
        }
 
-       uid, gid, mode, type_ := shared.ParseLXDFileHeaders(r.Header)
+       uid, gid, mode, type_, _ := shared.ParseLXDFileHeaders(r.Header)
        if type_ == "directory" {
                resp, err := HoistResponse(r, api.SyncResponse)
                if err != nil {
diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index b6a9d23..9c76606 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -205,3 +205,6 @@ This includes:
 
 ## file\_delete
 Implements DELETE in /1.0/containers/\<name\>/files
+
+## file\_append
+Implements the X-LXD-write header which can be one of "overwrite" or "append".
diff --git a/doc/rest-api.md b/doc/rest-api.md
index 4c3f5e0..d883338 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -830,6 +830,7 @@ The following headers may be set by the client:
  * X-LXD-uid: 0
  * X-LXD-gid: 0
  * X-LXD-mode: 0700
+ * X-LXD-write: overwrite (or append, introduced with API extension 
"file\_append")
 
 This is designed to be easily usable from the command line or even a web
 browser.
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 963c815..215f4a9 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -106,6 +106,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
                        "network_routes",
                        "storage",
                        "file_delete",
+                       "file_append",
                },
                APIStatus:  "stable",
                APIVersion: version.APIVersion,
diff --git a/lxd/container.go b/lxd/container.go
index 20a41dd..e4ef23c 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -370,7 +370,7 @@ type container interface {
        // File handling
        FileExists(path string) error
        FilePull(srcpath string, dstpath string) (int, int, os.FileMode, 
string, []string, error)
-       FilePush(srcpath string, dstpath string, uid int, gid int, mode int) 
error
+       FilePush(srcpath string, dstpath string, uid int, gid int, mode int, 
write string) error
        FileRemove(path string) error
 
        /* Command execution:
diff --git a/lxd/container_file.go b/lxd/container_file.go
index a4c1714..eb443a3 100644
--- a/lxd/container_file.go
+++ b/lxd/container_file.go
@@ -84,7 +84,11 @@ func containerFileGet(c container, path string, r 
*http.Request) Response {
 
 func containerFilePut(c container, path string, r *http.Request) Response {
        // Extract file ownership and mode from headers
-       uid, gid, mode, type_ := shared.ParseLXDFileHeaders(r.Header)
+       uid, gid, mode, type_, write := shared.ParseLXDFileHeaders(r.Header)
+
+       if !shared.StringInSlice(write, []string{"overwrite", "append"}) {
+               return BadRequest(fmt.Errorf("Bad file write mode: %s", write))
+       }
 
        if type_ == "file" {
                // Write file content to a tempfile
@@ -103,20 +107,20 @@ func containerFilePut(c container, path string, r 
*http.Request) Response {
                }
 
                // Transfer the file into the container
-               err = c.FilePush(temp.Name(), path, uid, gid, mode)
+               err = c.FilePush(temp.Name(), path, uid, gid, mode, write)
                if err != nil {
                        return InternalError(err)
                }
 
                return EmptySyncResponse
        } else if type_ == "directory" {
-               err := c.FilePush("", path, uid, gid, mode)
+               err := c.FilePush("", path, uid, gid, mode, write)
                if err != nil {
                        return InternalError(err)
                }
                return EmptySyncResponse
        } else {
-               return InternalError(fmt.Errorf("bad file type %s", type_))
+               return BadRequest(fmt.Errorf("Bad file type: %s", type_))
        }
 }
 
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 46600bf..8a62d9f 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -4505,7 +4505,7 @@ func (c *containerLXC) FilePull(srcpath string, dstpath 
string) (int, int, os.Fi
        return uid, gid, os.FileMode(mode), type_, dirEnts, nil
 }
 
-func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int, gid 
int, mode int) error {
+func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int, gid 
int, mode int, write string) error {
        var rootUid = 0
        var rootGid = 0
        var errStr string
@@ -4545,6 +4545,7 @@ func (c *containerLXC) FilePush(srcpath string, dstpath 
string, uid int, gid int
                fmt.Sprintf("%d", rootUid),
                fmt.Sprintf("%d", rootGid),
                fmt.Sprintf("%d", int(os.FileMode(0640)&os.ModePerm)),
+               write,
        ).CombinedOutput()
 
        // Tear down container storage if needed
@@ -4579,7 +4580,7 @@ func (c *containerLXC) FilePush(srcpath string, dstpath 
string, uid int, gid int
 
        if err != nil {
                return fmt.Errorf(
-                       "Error calling 'lxd forkputfile %s %d %s %s %d %d %d %d 
%d %d': err='%v'",
+                       "Error calling 'lxd forkputfile %s %d %s %s %d %d %d %d 
%d %d %s': err='%v'",
                        c.RootfsPath(),
                        c.InitPID(),
                        srcpath,
@@ -4590,6 +4591,7 @@ func (c *containerLXC) FilePush(srcpath string, dstpath 
string, uid int, gid int
                        rootUid,
                        rootGid,
                        int(os.FileMode(0640)&os.ModePerm),
+                       write,
                        err)
        }
 
diff --git a/lxd/main_nsexec.go b/lxd/main_nsexec.go
index a1239f4..67c3c14 100644
--- a/lxd/main_nsexec.go
+++ b/lxd/main_nsexec.go
@@ -87,16 +87,21 @@ int mkdir_p(const char *dir, mode_t mode)
        return 0;
 }
 
-int copy(int target, int source)
+int copy(int target, int source, bool append)
 {
        ssize_t n;
        char buf[1024];
 
-       if (ftruncate(target, 0) < 0) {
+       if (!append && ftruncate(target, 0) < 0) {
                error("error: truncate");
                return -1;
        }
 
+       if (append && lseek(target, 0, SEEK_END) < 0) {
+               error("error: seek");
+               return -1;
+       }
+
        while ((n = read(source, buf, 1024)) > 0) {
                if (write(target, buf, n) != n) {
                        error("error: write");
@@ -175,7 +180,7 @@ void attach_userns(int pid) {
        }
 }
 
-int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool 
is_put, uid_t uid, gid_t gid, mode_t mode, uid_t defaultUid, gid_t defaultGid, 
mode_t defaultMode) {
+int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool 
is_put, uid_t uid, gid_t gid, mode_t mode, uid_t defaultUid, gid_t defaultGid, 
mode_t defaultMode, bool append) {
        int host_fd = -1, container_fd = -1;
        int ret = -1;
        int container_open_flags;
@@ -273,7 +278,7 @@ int manip_file_in_ns(char *rootfs, int pid, char *host, 
char *container, bool is
                        }
                }
 
-               if (copy(container_fd, host_fd) < 0) {
+               if (copy(container_fd, host_fd, append) < 0) {
                        error("error: copy");
                        goto close_container;
                }
@@ -333,7 +338,7 @@ int manip_file_in_ns(char *rootfs, int pid, char *host, 
char *container, bool is
                        goto close_host;
                } else {
                        fprintf(stderr, "type: file\n");
-                       ret = copy(host_fd, container_fd);
+                       ret = copy(host_fd, container_fd, false);
                }
                fprintf(stderr, "type: %s", S_ISDIR(st.st_mode) ? "directory" : 
"file");
        }
@@ -497,8 +502,9 @@ void forkdofile(char *buf, char *cur, bool is_put, ssize_t 
size) {
        uid_t defaultUid = 0;
        gid_t defaultGid = 0;
        mode_t defaultMode = 0;
-       char *command = cur, *rootfs = NULL, *source = NULL, *target = NULL;
+       char *command = cur, *rootfs = NULL, *source = NULL, *target = NULL, 
*writeMode = NULL;
        pid_t pid;
+       bool append = false;
 
        ADVANCE_ARG_REQUIRED();
        rootfs = cur;
@@ -530,9 +536,14 @@ void forkdofile(char *buf, char *cur, bool is_put, ssize_t 
size) {
 
                ADVANCE_ARG_REQUIRED();
                defaultMode = atoi(cur);
+
+               ADVANCE_ARG_REQUIRED();
+               if (strcmp(cur, "append") == 0) {
+                       append = true;
+               }
        }
 
-       _exit(manip_file_in_ns(rootfs, pid, source, target, is_put, uid, gid, 
mode, defaultUid, defaultGid, defaultMode));
+       _exit(manip_file_in_ns(rootfs, pid, source, target, is_put, uid, gid, 
mode, defaultUid, defaultGid, defaultMode, append));
 }
 
 void forkcheckfile(char *buf, char *cur, bool is_put, ssize_t size) {
diff --git a/shared/util.go b/shared/util.go
index 69f83ac..1e0b878 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -123,7 +123,7 @@ func LogPath(path ...string) string {
        return filepath.Join(items...)
 }
 
-func ParseLXDFileHeaders(headers http.Header) (uid int, gid int, mode int, 
type_ string) {
+func ParseLXDFileHeaders(headers http.Header) (uid int, gid int, mode int, 
type_ string, write string) {
        uid, err := strconv.Atoi(headers.Get("X-LXD-uid"))
        if err != nil {
                uid = -1
@@ -152,7 +152,15 @@ func ParseLXDFileHeaders(headers http.Header) (uid int, 
gid int, mode int, type_
                type_ = "file"
        }
 
-       return uid, gid, mode, type_
+       write = headers.Get("X-LXD-write")
+       /* backwards compat: before "write" was introduced, we could only
+        * overwrite files
+        */
+       if write == "" {
+               write = "overwrite"
+       }
+
+       return uid, gid, mode, type_, write
 }
 
 func ReadToJSON(r io.Reader, req interface{}) error {

From a9e04686f712303d691afab8463df1675048f439 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 14 Feb 2017 16:57:55 -0500
Subject: [PATCH 11/11] network: Implement configurable DHCP lease time
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2835

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 doc/api-extensions.md  |  3 +++
 doc/configuration.md   |  2 ++
 lxd/api_1.0.go         |  1 +
 lxd/networks.go        | 18 ++++++++++++++----
 lxd/networks_config.go |  2 ++
 5 files changed, 22 insertions(+), 4 deletions(-)

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index 9c76606..dbf18c6 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -208,3 +208,6 @@ Implements DELETE in /1.0/containers/\<name\>/files
 
 ## file\_append
 Implements the X-LXD-write header which can be one of "overwrite" or "append".
+
+## network\_dhcp\_expiry
+Introduces "ipv4.dhcp.expiry" and "ipv6.dhcp.expiry" allowing to set the DHCP 
lease expiry time.
diff --git a/doc/configuration.md b/doc/configuration.md
index 981647e..b791fbd 100644
--- a/doc/configuration.md
+++ b/doc/configuration.md
@@ -355,6 +355,7 @@ tunnel.NAME.id                  | integer   | vxlan         
        | 0
 ipv4.address                    | string    | standard mode         | random 
unused subnet      | IPv4 address for the bridge (CIDR notation). Use "none" to 
turn off IPv4 or "auto" to generate a new one
 ipv4.nat                        | boolean   | ipv4 address          | false    
                 | Whether to NAT (will default to true if unset and a random 
ipv4.address is generated)
 ipv4.dhcp                       | boolean   | ipv4 address          | true     
                 | Whether to allocate addresses using DHCP
+ipv4.dhcp.expiry                | string    | ipv4 dhcp             | 1h       
                 | When to expire DHCP leases
 ipv4.dhcp.ranges                | string    | ipv4 dhcp             | all 
addresses             | Comma separated list of IP ranges to use for DHCP 
(FIRST-LAST format)
 ipv4.firewall                   | boolean   | ipv4 address          | true     
                 | Whether to generate filtering firewall rules for this network
 ipv4.routes                     | string    | ipv4 address          | -        
                 | Comma separated list of additional IPv4 CIDR subnets to 
route to the bridge
@@ -362,6 +363,7 @@ ipv4.routing                    | boolean   | ipv4 address  
        | true
 ipv6.address                    | string    | standard mode         | random 
unused subnet      | IPv6 address for the bridge (CIDR notation). Use "none" to 
turn off IPv6 or "auto" to generate a new one
 ipv6.nat                        | boolean   | ipv6 address          | false    
                 | Whether to NAT (will default to true if unset and a random 
ipv6.address is generated)
 ipv6.dhcp                       | boolean   | ipv6 address          | true     
                 | Whether to provide additional network configuration over DHCP
+ipv6.dhcp.expiry                | string    | ipv6 dhcp             | 1h       
                 | When to expire DHCP leases
 ipv6.dhcp.stateful              | boolean   | ipv6 dhcp             | false    
                 | Whether to allocate addresses using DHCP
 ipv6.dhcp.ranges                | string    | ipv6 stateful dhcp    | all 
addresses             | Comma separated list of IPv6 ranges to use for DHCP 
(FIRST-LAST format)
 ipv6.firewall                   | boolean   | ipv6 address          | true     
                 | Whether to generate filtering firewall rules for this network
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 215f4a9..77bbc3e 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -107,6 +107,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
                        "storage",
                        "file_delete",
                        "file_append",
+                       "network_dhcp_expiry",
                },
                APIStatus:  "stable",
                APIVersion: version.APIVersion,
diff --git a/lxd/networks.go b/lxd/networks.go
index 63a298c..8a03d56 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -742,13 +742,18 @@ func (n *network) Start() error {
                                dnsmasqCmd = append(dnsmasqCmd, 
[]string{"--dhcp-no-override", "--dhcp-authoritative", 
fmt.Sprintf("--dhcp-leasefile=%s", shared.VarPath("networks", n.name, 
"dnsmasq.leases")), fmt.Sprintf("--dhcp-hostsfile=%s", 
shared.VarPath("networks", n.name, "dnsmasq.hosts"))}...)
                        }
 
+                       expiry := "1h"
+                       if n.config["ipv4.dhcp.expiry"] != "" {
+                               expiry = n.config["ipv4.dhcp.expiry"]
+                       }
+
                        if n.config["ipv4.dhcp.ranges"] != "" {
                                for _, dhcpRange := range 
strings.Split(n.config["ipv4.dhcp.ranges"], ",") {
                                        dhcpRange = strings.TrimSpace(dhcpRange)
-                                       dnsmasqCmd = append(dnsmasqCmd, 
[]string{"--dhcp-range", strings.Replace(dhcpRange, "-", ",", -1)}...)
+                                       dnsmasqCmd = append(dnsmasqCmd, 
[]string{"--dhcp-range", fmt.Sprintf("%s,%s", strings.Replace(dhcpRange, "-", 
",", -1), expiry)}...)
                                }
                        } else {
-                               dnsmasqCmd = append(dnsmasqCmd, 
[]string{"--dhcp-range", fmt.Sprintf("%s,%s", networkGetIP(subnet, 2).String(), 
networkGetIP(subnet, -2).String())}...)
+                               dnsmasqCmd = append(dnsmasqCmd, 
[]string{"--dhcp-range", fmt.Sprintf("%s,%s,%s", networkGetIP(subnet, 
2).String(), networkGetIP(subnet, -2).String(), expiry)}...)
                        }
                }
 
@@ -821,14 +826,19 @@ func (n *network) Start() error {
                                dnsmasqCmd = append(dnsmasqCmd, 
[]string{"--dhcp-no-override", "--dhcp-authoritative", 
fmt.Sprintf("--dhcp-leasefile=%s", shared.VarPath("networks", n.name, 
"dnsmasq.leases")), fmt.Sprintf("--dhcp-hostsfile=%s", 
shared.VarPath("networks", n.name, "dnsmasq.hosts"))}...)
                        }
 
+                       expiry := "1h"
+                       if n.config["ipv6.dhcp.expiry"] != "" {
+                               expiry = n.config["ipv6.dhcp.expiry"]
+                       }
+
                        if shared.IsTrue(n.config["ipv6.dhcp.stateful"]) {
                                if n.config["ipv6.dhcp.ranges"] != "" {
                                        for _, dhcpRange := range 
strings.Split(n.config["ipv6.dhcp.ranges"], ",") {
                                                dhcpRange = 
strings.TrimSpace(dhcpRange)
-                                               dnsmasqCmd = append(dnsmasqCmd, 
[]string{"--dhcp-range", fmt.Sprintf("%s", strings.Replace(dhcpRange, "-", ",", 
-1))}...)
+                                               dnsmasqCmd = append(dnsmasqCmd, 
[]string{"--dhcp-range", fmt.Sprintf("%s,%s", strings.Replace(dhcpRange, "-", 
",", -1), expiry)}...)
                                        }
                                } else {
-                                       dnsmasqCmd = append(dnsmasqCmd, 
[]string{"--dhcp-range", fmt.Sprintf("%s,%s", networkGetIP(subnet, 2), 
networkGetIP(subnet, -1))}...)
+                                       dnsmasqCmd = append(dnsmasqCmd, 
[]string{"--dhcp-range", fmt.Sprintf("%s,%s,%s", networkGetIP(subnet, 2), 
networkGetIP(subnet, -1), expiry)}...)
                                }
                        } else {
                                dnsmasqCmd = append(dnsmasqCmd, 
[]string{"--dhcp-range", fmt.Sprintf("::,constructor:%s,ra-stateless,ra-names", 
n.name)}...)
diff --git a/lxd/networks_config.go b/lxd/networks_config.go
index 0270045..fd6b17f 100644
--- a/lxd/networks_config.go
+++ b/lxd/networks_config.go
@@ -62,6 +62,7 @@ var networkConfigKeys = map[string]func(value string) error{
        "ipv4.firewall":    shared.IsBool,
        "ipv4.nat":         shared.IsBool,
        "ipv4.dhcp":        shared.IsBool,
+       "ipv4.dhcp.expiry": shared.IsAny,
        "ipv4.dhcp.ranges": shared.IsAny,
        "ipv4.routes":      shared.IsAny,
        "ipv4.routing":     shared.IsBool,
@@ -76,6 +77,7 @@ var networkConfigKeys = map[string]func(value string) error{
        "ipv6.firewall":      shared.IsBool,
        "ipv6.nat":           shared.IsBool,
        "ipv6.dhcp":          shared.IsBool,
+       "ipv6.dhcp.expiry":   shared.IsAny,
        "ipv6.dhcp.stateful": shared.IsBool,
        "ipv6.dhcp.ranges":   shared.IsAny,
        "ipv6.routes":        shared.IsAny,
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to