[lxc-devel] [pylxd/master] Add live parameter for migrations

ajkavanagh on Github lxc-bot at linuxcontainers.org
Thu May 2 10:19:06 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 550 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20190502/ff423bbe/attachment.bin>
-------------- next part --------------
From 5c4663deef35c3041b2981580cf0903a5f3c7051 Mon Sep 17 00:00:00 2001
From: Alex Kavanagh <alex.kavanagh at canonical.com>
Date: Thu, 2 May 2019 11:15:43 +0100
Subject: [PATCH] Add live parameter for migrations

The LXD api provides a parameter to kick of live migrations.  See
https://github.com/lxc/lxd/blob/master/doc/rest-api.md#post-optional-targetmember-1
for further details.

Closes: #338
Signed-off-by: Alex Kavanagh <alex.kavanagh at canonical.com>
---
 doc/source/containers.rst            | 10 ++++++-
 pylxd/models/container.py            | 42 ++++++++++++++++++++++------
 pylxd/tests/models/test_container.py |  7 +++--
 3 files changed, 47 insertions(+), 12 deletions(-)

diff --git a/doc/source/containers.rst b/doc/source/containers.rst
index 48d5ff92..00ec7070 100644
--- a/doc/source/containers.rst
+++ b/doc/source/containers.rst
@@ -68,7 +68,9 @@ Container methods
     an url to an interactive websocket and the execution only starts after a client connected to the websocket.
   - `migrate` - Migrate the container. The first argument is a client
     connection to the destination server. This call is asynchronous, so
-    `wait=True` is optional. The container on the new client is returned.
+    ``wait=True`` is optional. The container on the new client is returned.  If
+    ``live=True`` is passed to the function call, then the container is live
+    migrated (see the LXD documentation for further details).
   - `publish` - Publish the container as an image.  Note the container must be stopped
     in order to use this method.  If `wait=True` is passed, then the image is returned.
 
@@ -165,6 +167,12 @@ the source server has to be reachable by the destination server otherwise the mi
 
 This will migrate the container from source server to destination server
 
+To migrate a live container, user the ``live=True`` parameter:
+
+..code-block:: python
+
+    cont.migrate(client__destination, live=True, wait=True)
+
 If you want an interactive shell in the container, you can attach to it via a websocket.
 
 .. code-block:: python
diff --git a/pylxd/models/container.py b/pylxd/models/container.py
index d6dc4694..856ec291 100644
--- a/pylxd/models/container.py
+++ b/pylxd/models/container.py
@@ -475,15 +475,31 @@ def raw_interactive_execute(self, commands, environment=None):
         return {'ws': '{}?secret={}'.format(parsed.path, fds['0']),
                 'control': '{}?secret={}'.format(parsed.path, fds['control'])}
 
-    def migrate(self, new_client, wait=False):
+    def migrate(self, new_client, live=False, wait=False):
         """Migrate a container.
 
         Destination host information is contained in the client
         connection passed in.
 
-        If the container is running, it either must be shut down
-        first or criu must be installed on the source and destination
-        machines.
+        If the `live` param is True, then a live migration is attempted,
+        otherwise a non live one is running.
+
+        If the container is running for live migration, it either must be shut
+        down first or criu must be installed on the source and destination
+        machines and the `live` param should be True.
+
+        :param new_client: the pylxd client connection to migrate the container
+            to.
+        :type new_client: :class:`pylxd.client.Client`
+        :param live: whether to perform a live migration
+        :type live: bool
+        :param wait: if True, wait for the migration to complete
+        :type wait: bool
+        :raises: LXDAPIException if any of the API calls fail.
+        :raises: ValueError if source of target are local connections
+        :returns: the response from LXD of the new container (the target of the
+            migration and not the operation if waited on.)
+        :rtype: :class:`requests.Response`
         """
         if self.api.scheme in ('http+unix',):
             raise ValueError('Cannot migrate from a local client connection')
@@ -491,7 +507,7 @@ def migrate(self, new_client, wait=False):
         if self.status_code == 103:
             try:
                 res = new_client.containers.create(
-                    self.generate_migration_data(), wait=wait)
+                    self.generate_migration_data(live), wait=wait)
             except LXDAPIException as e:
                 if e.response.status_code == 103:
                     self.delete()
@@ -500,19 +516,29 @@ def migrate(self, new_client, wait=False):
                     raise e
         else:
             res = new_client.containers.create(
-                self.generate_migration_data(), wait=wait)
+                self.generate_migration_data(live), wait=wait)
         self.delete()
         return res
 
-    def generate_migration_data(self):
+    def generate_migration_data(self, live=False):
         """Generate the migration data.
 
         This method can be used to handle migrations where the client
         connection uses the local unix socket. For more information on
         migration, see `Container.migrate`.
+
+        :param live: Whether to include "live": "true" in the migration
+        :type live: bool
+        :raises: LXDAPIException if the request to migrate fails
+        :returns: dictionary of migration data suitable to send to an new
+            client to complete a migration.
+        :rtype: Dict[str, ANY]
         """
         self.sync()  # Make sure the object isn't stale
-        response = self.api.post(json={'migration': True})
+        _json = {'migration': True}
+        if live:
+            _json['live'] = True
+        response = self.api.post(json=_json)
         operation = self.client.operations.get(response.json()['operation'])
         operation_url = self.client.api.operations[operation.id]._api_endpoint
         secrets = response.json()['metadata']['metadata']
diff --git a/pylxd/tests/models/test_container.py b/pylxd/tests/models/test_container.py
index 795bebdc..f8447f7c 100644
--- a/pylxd/tests/models/test_container.py
+++ b/pylxd/tests/models/test_container.py
@@ -285,7 +285,7 @@ def test_migrate_exception_error(self, generate_migration_data):
         from pylxd.client import Client
         from pylxd.exceptions import LXDAPIException
 
-        def generate_exception():
+        def generate_exception(*args, **kwargs):
             response = mock.Mock()
             response.status_code = 400
             raise LXDAPIException(response)
@@ -309,17 +309,18 @@ def test_migrate_exception_running(self, generate_migration_data):
             self.client, name='an-container')
         an_container.status_code = 103
 
-        def generate_exception():
+        def generate_exception(*args, **kwargs):
             response = mock.Mock()
             response.status_code = 103
             raise LXDAPIException(response)
 
         generate_migration_data.side_effect = generate_exception
 
-        an_migrated_container = an_container.migrate(client2)
+        an_migrated_container = an_container.migrate(client2, live=True)
 
         self.assertEqual('an-container', an_migrated_container.name)
         self.assertEqual(client2, an_migrated_container.client)
+        generate_migration_data.assert_called_once_with(True)
 
     def test_migrate_started(self):
         """A container is migrated."""


More information about the lxc-devel mailing list