[Kimchi-devel] [PATCH 02/10] snapshot: Create domain snapshots

Aline Manera alinefm at linux.vnet.ibm.com
Wed Nov 12 15:47:02 UTC 2014


On 11/12/2014 11:08 AM, Crístian Viana wrote:
> A new command is added to create a new snapshot:
>
> POST /vms/<vm-name>/snapshots {'name': '<snapshot-name>'}
>
> It creates a new snapshot with the current state of the VM. Currently,
> due to Kimchi not supporting paused states, snapshots can only be
> created when the VM is shut off. The parameter 'name' is optional;
> Kimchi will create a default value based on the current time if it's not
> provided.
>
> Signed-off-by: Crístian Viana <vianac at linux.vnet.ibm.com>
> ---
>   docs/API.md                        |  5 ++
>   src/kimchi/control/vm/snapshots.py | 44 ++++++++++++++++++
>   src/kimchi/i18n.py                 |  3 ++
>   src/kimchi/mockmodel.py            | 49 ++++++++++++++++++++
>   src/kimchi/model/vmsnapshots.py    | 95 ++++++++++++++++++++++++++++++++++++++
>   tests/test_rest.py                 | 14 ++++++
>   6 files changed, 210 insertions(+)
>   create mode 100644 src/kimchi/control/vm/snapshots.py
>   create mode 100644 src/kimchi/model/vmsnapshots.py
>
> diff --git a/docs/API.md b/docs/API.md
> index 9b866f3..fe1c3cf 100644
> --- a/docs/API.md
> +++ b/docs/API.md
> @@ -188,6 +188,11 @@ Represents a snapshot of the Virtual Machine's primary monitor.
>       * type: The type of the assigned device.
>   * **DELETE**: Detach the host device from VM.
>   
> +### Sub-collection: Virtual Machine Snapshots
> +**URI:** /vms/*:name*/snapshots
> +* **POST**: Create a new snapshot on a VM.
> +    * name: The snapshot name (optional, defaults to a value based on the
> +            current time).
>   
>   ### Collection: Templates
>   
> diff --git a/src/kimchi/control/vm/snapshots.py b/src/kimchi/control/vm/snapshots.py
> new file mode 100644
> index 0000000..5650435
> --- /dev/null
> +++ b/src/kimchi/control/vm/snapshots.py
> @@ -0,0 +1,44 @@
> +#
> +# Project Kimchi
> +#
> +# Copyright IBM, Corp. 2014
> +#
> +# This library is free software; you can redistribute it and/or
> +# modify it under the terms of the GNU Lesser General Public
> +# License as published by the Free Software Foundation; either
> +# version 2.1 of the License, or (at your option) any later version.
> +#
> +# This library is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +# Lesser General Public License for more details.
> +#
> +# You should have received a copy of the GNU Lesser General Public
> +# License along with this library; if not, write to the Free Software
> +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
> +
> +from kimchi.control.base import AsyncCollection, Resource
> +from kimchi.control.utils import UrlSubNode
> +
> +
> + at UrlSubNode('snapshots')
> +class VMSnapshots(AsyncCollection):
> +    def __init__(self, model, vm):
> +        super(VMSnapshots, self).__init__(model)
> +        self.resource = VMSnapshot
> +        self.vm = vm
> +        self.resource_args = [self.vm, ]
> +        self.model_args = [self.vm, ]
> +
> +
> +class VMSnapshot(Resource):
> +    def __init__(self, model, vm, ident):
> +        super(VMSnapshot, self).__init__(model, ident)
> +        self.vm = vm
> +        self.ident = ident
> +        self.model_args = [self.vm, self.ident]
> +        self.uri_fmt = '/vms/%s/snapshots/%s'
> +
> +    @property
> +    def data(self):
> +        return self.info
> diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py
> index e823f2b..9c37931 100644
> --- a/src/kimchi/i18n.py
> +++ b/src/kimchi/i18n.py
> @@ -310,4 +310,7 @@ messages = {
>       "KCHREPOS0026E": _("Unable to add repository. Details: '%(err)s'"),
>       "KCHREPOS0027E": _("Unable to remove repository. Details: '%(err)s'"),
>       "KCHREPOS0028E": _("Configuration items: '%(items)s' are not supported by repository manager"),
> +
> +    "KCHSNAP0001E": _("Virtual machine '%(vm)s' must be stopped before creating a snapshot on it."),
> +    "KCHSNAP0002E": _("Unable to create snapshot '%(name)s' on virtual machine '%(vm)s'. Details: %(err)s"),
>   }
> diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py
> index 626ef35..e06b01f 100644
> --- a/src/kimchi/mockmodel.py
> +++ b/src/kimchi/mockmodel.py
> @@ -965,6 +965,42 @@ class MockModel(object):
>               info['model'] = params['model']
>           return mac
>   
> +    def vmsnapshots_create(self, vm_name, params):
> +        try:
> +            name = params['name']
> +        except KeyError:
> +            name = unicode(int(time.time()))
> +

name = params.get('name', unicode(int(time.time())))

> +        vm = self._get_vm(vm_name)
> +        if vm.info['state'] != 'shutoff':
> +            raise InvalidOperation('KCHSNAP0001E', {'vm': vm_name})
> +
> +        params = {'vm_name': vm_name, 'name': name}
> +        taskid = self.add_task(u'/vms/%s/snapshots/%s' % (vm_name, name),
> +                               self._vmsnapshots_create_task, params)
> +
> +        return self.task_lookup(taskid)
> +
> +    def _vmsnapshots_create_task(self, cb, params):
> +        vm_name = params['vm_name']
> +        name = params['name']
> +
> +        vm = self._get_vm(vm_name)
> +
> +        parent = u''
> +        for sn, s in vm.snapshots.iteritems():
> +            if s.info['current']:
> +                s.info['current'] = False
> +                parent = sn
> +                break
> +
> +        snap_info = {'name': name,
> +                     'parent': parent,
> +                     'state': vm.info['state']}
> +        vm.snapshots[name] = MockVMSnapshot(vm_name, name, snap_info)
> +
> +        cb('OK', True)
> +
>       def tasks_get_list(self):
>           with self.objstore as session:
>               return session.get_list('task')
> @@ -1230,6 +1266,7 @@ class MockVM(object):
>           ifaces = [MockVMIface(net) for net in self.networks]
>           self.storagedevices = {}
>           self.ifaces = dict([(iface.info['mac'], iface) for iface in ifaces])
> +        self.snapshots = {}
>   
>           stats = {'cpu_utilization': 20,
>                    'net_throughput': 35,
> @@ -1581,6 +1618,18 @@ class MockDevices(object):
>                              'path': '/sys/devices/pci0000:00/0000:40:00.0/2'}}
>   
>   
> +class MockVMSnapshot(object):
> +    def __init__(self, vm_name, name, params={}):
> +        self.vm = vm_name
> +        self.name = name
> +
> +        self.info = {'created': params.get('created',
> +                                           unicode(int(time.time()))),
> +                     'current': params.get('current', True),
> +                     'parent': params.get('parent', u''),
> +                     'state': params.get('state', u'shutoff')}
> +
> +
>   def get_mock_environment():
>       model = MockModel()
>       for i in xrange(5):
> diff --git a/src/kimchi/model/vmsnapshots.py b/src/kimchi/model/vmsnapshots.py
> new file mode 100644
> index 0000000..8701c03
> --- /dev/null
> +++ b/src/kimchi/model/vmsnapshots.py
> @@ -0,0 +1,95 @@
> +#
> +# Project Kimchi
> +#
> +# Copyright IBM, Corp. 2014
> +#
> +# This library is free software; you can redistribute it and/or
> +# modify it under the terms of the GNU Lesser General Public
> +# License as published by the Free Software Foundation; either
> +# version 2.1 of the License, or (at your option) any later version.
> +#
> +# This library is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +# Lesser General Public License for more details.
> +#
> +# You should have received a copy of the GNU Lesser General Public
> +# License along with this library; if not, write to the Free Software
> +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
> +
> +import time
> +
> +import libvirt
> +import lxml.etree as ET
> +from lxml.builder import E
> +
> +from kimchi.exception import InvalidOperation, NotFoundError, OperationFailed
> +from kimchi.model.tasks import TaskModel
> +from kimchi.model.vms import DOM_STATE_MAP, VMModel
> +from kimchi.utils import add_task
> +
> +
> +class VMSnapshotsModel(object):
> +    def __init__(self, **kargs):
> +        self.conn = kargs['conn']
> +        self.objstore = kargs['objstore']
> +        self.task = TaskModel(**kargs)
> +
> +    def create(self, vm_name, params={}):
> +        """Create a snapshot with the current domain state.
> +
> +        The VM must be stopped before creating a snapshot on it; otherwise, an
> +        exception will be raised.
> +
> +        Parameters:
> +        vm_name -- the name of the VM where the snapshot will be created.
> +        params -- a dict with the following values:
> +            "name": The snapshot name (optional). If ommited, a default value
> +            based on the current time will be used.
> +
> +        Return:
> +        A Task running the operation.
> +        """
> +        vir_dom = VMModel.get_vm(vm_name, self.conn)
> +        if DOM_STATE_MAP[vir_dom.info()[0]] != u'shutoff':
> +            raise InvalidOperation('KCHSNAP0001E', {'vm': vm_name})
> +


> +        try:
> +            name = params['name']
> +        except KeyError:
> +            name = unicode(int(time.time()))
> +

name = params.get('name', unicode(int(time.time())))

> +        task_params = {'vm_name': vm_name, 'name': name}
> +
> +        taskid = add_task(u'/vms/%s/snapshots/%s' % (vm_name, name),
> +                          self._create_task, self.objstore, task_params)
> +        return self.task.lookup(taskid)
> +
> +    def _create_task(self, cb, params):
> +        """Asynchronous function which actually creates the snapshot.
> +
> +        Parameters:
> +        cb -- a callback function to signal the Task's progress.
> +        params -- a dict with the following values:
> +            "vm_name": the name of the VM where the snapshot will be created.
> +            "name": the snapshot name.
> +        """
> +        vm_name = params['vm_name']
> +        name = params['name']
> +
> +        cb('building snapshot XML')
> +        root_elem = E.domainsnapshot()
> +        root_elem.append(E.name(name))
> +        xml = ET.tostring(root_elem, encoding='utf-8')
> +
> +        try:
> +            cb('fetching snapshot domain')
> +            vir_dom = VMModel.get_vm(vm_name, self.conn)
> +            cb('creating snapshot')
> +            vir_dom.snapshotCreateXML(xml, 0)
> +        except (NotFoundError, OperationFailed, libvirt.libvirtError), e:
> +            raise OperationFailed('KCHSNAP0002E',
> +                                  {'name': name, 'vm': vm_name,
> +                                   'err': e.message})
> +
> +        cb('OK', True)
> diff --git a/tests/test_rest.py b/tests/test_rest.py
> index 6770647..7e6d684 100644
> --- a/tests/test_rest.py
> +++ b/tests/test_rest.py
> @@ -346,6 +346,10 @@ class RestTests(unittest.TestCase):
>           resp = self.request('/vms/test-vm/clone', '{}', 'POST')
>           self.assertEquals(400, resp.status)
>   
> +        # Create a snapshot on a running VM
> +        resp = self.request('/vms/test-vm/snapshots', '{}', 'POST')
> +        self.assertEquals(400, resp.status)
> +
>           # Force poweroff the VM
>           resp = self.request('/vms/test-vm/poweroff', '{}', 'POST')
>           vm = json.loads(self.request('/vms/test-vm').read())
> @@ -382,6 +386,16 @@ class RestTests(unittest.TestCase):
>   
>           self.assertEquals(original_vm_info, clone_vm_info)
>   
> +        # Create a snapshot on a stopped VM
> +        params = {'name': 'test-snap'}
> +        resp = self.request('/vms/test-vm/snapshots', json.dumps(params),
> +                            'POST')
> +        self.assertEquals(202, resp.status)
> +        task = json.loads(resp.read())
> +        wait_task(self._task_lookup, task['id'])
> +        task = json.loads(self.request('/tasks/%s' % task['id']).read())
> +        self.assertEquals('finished', task['status'])
> +
>           # Delete the VM
>           resp = self.request('/vms/test-vm', '{}', 'DELETE')
>           self.assertEquals(204, resp.status)




More information about the Kimchi-devel mailing list