Пример #1
0
    def delete(self, vm_name, dev_name):
        try:
            bus_type = self.lookup(vm_name, dev_name)['bus']
            dom = VMModel.get_vm(vm_name, self.conn)
        except NotFoundError:
            raise

        if (bus_type not in HOTPLUG_TYPE
                and DOM_STATE_MAP[dom.info()[0]] != 'shutoff'):
            raise InvalidOperation('KCHVMSTOR0011E')

        try:
            disk = get_device_node(dom, dev_name)
            path = get_vm_disk_info(dom, dev_name)['path']
            if path is None or len(path) < 1:
                path = self.lookup(vm_name, dev_name)['path']
            # This has to be done before it's detached. If it wasn't
            #   in the obj store, its ref count would have been updated
            #   by get_disk_used_by()
            if path is not None:
                used_by = get_disk_used_by(self.objstore, self.conn, path)
            else:
                wok_log.error("Unable to decrement volume used_by on"
                              " delete because no path could be found.")
            dom.detachDeviceFlags(etree.tostring(disk),
                                  get_vm_config_flag(dom, 'all'))
        except Exception as e:
            raise OperationFailed("KCHVMSTOR0010E", {'error': e.message})

        if used_by is not None and vm_name in used_by:
            used_by.remove(vm_name)
            set_disk_used_by(self.objstore, path, used_by)
        else:
            wok_log.error("Unable to update %s:%s used_by on delete." %
                          (vm_name, dev_name))
Пример #2
0
    def delete(self, vm_name, dev_name):
        try:
            bus_type = self.lookup(vm_name, dev_name)['bus']
            dom = VMModel.get_vm(vm_name, self.conn)
        except NotFoundError:
            raise

        if (bus_type not in HOTPLUG_TYPE and
                DOM_STATE_MAP[dom.info()[0]] != 'shutoff'):
            raise InvalidOperation('KCHVMSTOR0011E')

        try:
            disk = get_device_node(dom, dev_name)
            path = get_vm_disk_info(dom, dev_name)['path']
            if path is None or len(path) < 1:
                path = self.lookup(vm_name, dev_name)['path']
            # This has to be done before it's detached. If it wasn't
            #   in the obj store, its ref count would have been updated
            #   by get_disk_used_by()
            if path is not None:
                used_by = get_disk_used_by(self.objstore, self.conn, path)
            else:
                wok_log.error("Unable to decrement volume used_by on"
                              " delete because no path could be found.")
            dom.detachDeviceFlags(etree.tostring(disk),
                                  get_vm_config_flag(dom, 'all'))
        except Exception as e:
            raise OperationFailed("KCHVMSTOR0010E", {'error': e.message})

        if used_by is not None and vm_name in used_by:
            used_by.remove(vm_name)
            set_disk_used_by(self.objstore, path, used_by)
        else:
            wok_log.error("Unable to update %s:%s used_by on delete."
                          % (vm_name, dev_name))
Пример #3
0
    def update(self, vm_name, dev_name, params):
        old_disk_used_by = None
        new_disk_used_by = None

        dom = VMModel.get_vm(vm_name, self.conn)

        dev_info = self.lookup(vm_name, dev_name)
        if dev_info['type'] != 'cdrom':
            raise InvalidOperation("KCHVMSTOR0006E")

        params['path'] = params.get('path', '')
        old_disk_path = dev_info['path']
        new_disk_path = params['path']
        if new_disk_path != old_disk_path:
            # An empty path means a CD-ROM was empty or ejected:
            if old_disk_path is not '':
                old_disk_used_by = get_disk_used_by(self.objstore, self.conn,
                                                    old_disk_path)
            if new_disk_path is not '':
                new_disk_used_by = get_disk_used_by(self.objstore, self.conn,
                                                    new_disk_path)

        dev_info.update(params)
        dev, xml = get_disk_xml(dev_info)

        try:
            dom.updateDeviceFlags(xml, get_vm_config_flag(dom, 'all'))
        except Exception as e:
            raise OperationFailed("KCHVMSTOR0009E", {'error': e.message})

        try:
            if old_disk_used_by is not None and \
               vm_name in old_disk_used_by:
                old_disk_used_by.remove(vm_name)
                set_disk_used_by(self.objstore, old_disk_path,
                                 old_disk_used_by)
            if new_disk_used_by is not None:
                new_disk_used_by.append(vm_name)
                set_disk_used_by(self.objstore, new_disk_path,
                                 new_disk_used_by)
        except Exception as e:
            wok_log.error("Unable to update dev used_by on update due to"
                          " %s:" % e.message)
        return dev
Пример #4
0
    def update(self, vm_name, dev_name, params):
        old_disk_used_by = None
        new_disk_used_by = None

        dom = VMModel.get_vm(vm_name, self.conn)

        dev_info = self.lookup(vm_name, dev_name)
        if dev_info['type'] != 'cdrom':
            raise InvalidOperation("KCHVMSTOR0006E")

        params['path'] = params.get('path', '')
        old_disk_path = dev_info['path']
        new_disk_path = params['path']
        if new_disk_path != old_disk_path:
            # An empty path means a CD-ROM was empty or ejected:
            if old_disk_path is not '':
                old_disk_used_by = get_disk_used_by(
                    self.objstore, self.conn, old_disk_path)
            if new_disk_path is not '':
                new_disk_used_by = get_disk_used_by(
                    self.objstore, self.conn, new_disk_path)

        dev_info.update(params)
        dev, xml = get_disk_xml(dev_info)

        try:
            dom.updateDeviceFlags(xml, get_vm_config_flag(dom, 'all'))
        except Exception as e:
            raise OperationFailed("KCHVMSTOR0009E", {'error': e.message})

        try:
            if old_disk_used_by is not None and \
               vm_name in old_disk_used_by:
                old_disk_used_by.remove(vm_name)
                set_disk_used_by(self.objstore, old_disk_path,
                                 old_disk_used_by)
            if new_disk_used_by is not None:
                new_disk_used_by.append(vm_name)
                set_disk_used_by(self.objstore, new_disk_path,
                                 new_disk_used_by)
        except Exception as e:
            wok_log.error("Unable to update dev used_by on update due to"
                          " %s:" % e.message)
        return dev
Пример #5
0
class StorageVolumesModel(object):
    def __init__(self, **kargs):
        self.conn = kargs['conn']
        self.objstore = kargs['objstore']
        self.task = TaskModel(**kargs)

    def create(self, pool_name, params):
        vol_source = ['url', 'capacity']

        name = params.get('name')

        index_list = list(i for i in range(len(vol_source))
                          if vol_source[i] in params)
        if len(index_list) != 1:
            raise InvalidParameter("KCHVOL0018E",
                                   {'param': ",".join(vol_source)})

        create_param = vol_source[index_list[0]]

        # Verify if the URL is valid
        if create_param == 'url':
            url = params['url']
            try:
                urllib2.urlopen(url).close()
            except:
                raise InvalidParameter('KCHVOL0022E', {'url': url})

        all_vol_names = self.get_list(pool_name)

        if name is None:
            # the methods listed in 'REQUIRE_NAME_PARAMS' cannot have
            # 'name' == None
            if create_param in REQUIRE_NAME_PARAMS:
                raise InvalidParameter('KCHVOL0016E')

            # if 'name' is omitted - except for the methods listed in
            # 'REQUIRE_NAME_PARAMS' - the default volume name will be the
            # file/URL basename.
            if create_param == 'url':
                name = os.path.basename(params['url'])
            else:
                name = 'upload-%s' % int(time.time())

            name = get_unique_file_name(all_vol_names, name)
            params['name'] = name

        try:
            create_func = getattr(self, '_create_volume_with_%s' %
                                        create_param)
        except AttributeError:
            raise InvalidParameter("KCHVOL0019E", {'param': create_param})

        pool_info = StoragePoolModel(conn=self.conn,
                                     objstore=self.objstore).lookup(pool_name)
        if pool_info['type'] in READONLY_POOL_TYPE:
            raise InvalidParameter("KCHVOL0012E", {'type': pool_info['type']})
        if pool_info['state'] == 'inactive':
            raise InvalidParameter('KCHVOL0003E', {'pool': pool_name,
                                                   'volume': name})
        if name in all_vol_names:
            raise InvalidParameter('KCHVOL0001E', {'name': name})

        params['pool'] = pool_name
        targeturi = '/plugins/kimchi/storagepools/%s/storagevolumes/%s' \
                    % (pool_name, name)
        taskid = add_task(targeturi, create_func, self.objstore, params)
        return self.task.lookup(taskid)

    def _create_volume_with_capacity(self, cb, params):
        pool_name = params.pop('pool')
        vol_xml = """
        <volume>
          <name>%(name)s</name>
          <allocation unit='bytes'>%(allocation)s</allocation>
          <capacity unit='bytes'>%(capacity)s</capacity>
          <source>
          </source>
          <target>
            <format type='%(format)s'/>
          </target>
        </volume>
        """
        params.setdefault('allocation', 0)
        params.setdefault('format', 'qcow2')

        name = params['name']
        try:
            pool = StoragePoolModel.get_storagepool(pool_name, self.conn)
            xml = vol_xml % params
        except KeyError, item:
            raise MissingParameter("KCHVOL0004E", {'item': str(item),
                                                   'volume': name})

        try:
            pool.createXML(xml, 0)
        except libvirt.libvirtError as e:
            raise OperationFailed("KCHVOL0007E",
                                  {'name': name, 'pool': pool,
                                   'err': e.get_error_message()})

        vol_info = StorageVolumeModel(conn=self.conn,
                                      objstore=self.objstore).lookup(pool_name,
                                                                     name)

        vol_path = vol_info['path']
        set_disk_used_by(self.objstore, vol_info['path'], [])

        if params.get('upload', False):
            upload_volumes[vol_path] = {'lock': threading.Lock(),
                                        'offset': 0, 'cb': cb}
            cb('ready for upload')
        else:
            cb('OK', True)
Пример #6
0
class StorageVolumeModel(object):
    def __init__(self, **kargs):
        self.conn = kargs['conn']
        self.objstore = kargs['objstore']
        self.task = TaskModel(**kargs)
        self.storagevolumes = StorageVolumesModel(**kargs)
        self.storagepool = StoragePoolModel(**kargs)

    @staticmethod
    def get_storagevolume(poolname, name, conn):
        pool = StoragePoolModel.get_storagepool(poolname, conn)
        if not pool.isActive():
            raise InvalidOperation("KCHVOL0006E", {'name': pool})
        try:
            return pool.storageVolLookupByName(name.encode("utf-8"))
        except libvirt.libvirtError as e:
            if e.get_error_code() == libvirt.VIR_ERR_NO_STORAGE_VOL:
                raise NotFoundError("KCHVOL0002E", {'name': name,
                                                    'pool': poolname})
            else:
                raise

    def lookup(self, pool, name):
        vol = StorageVolumeModel.get_storagevolume(pool, name, self.conn)
        path = vol.path()
        info = vol.info()
        xml = vol.XMLDesc(0)
        try:
            fmt = xpath_get_text(xml, "/volume/target/format/@type")[0]
        except IndexError:
            # Not all types of libvirt storage can provide volume format
            # infomation. When there is no format information, we assume
            # it's 'raw'.
            fmt = 'raw'

        iso_img = None

        # 'raw' volumes from 'logical' pools may actually be 'iso';
        # libvirt always reports them as 'raw'
        pool_info = self.storagepool.lookup(pool)
        if pool_info['type'] == 'logical' and fmt == 'raw':
            try:
                iso_img = IsoImage(path)
            except IsoFormatError:
                # not 'iso' afterall
                pass
            else:
                fmt = 'iso'

        # 'raw' volumes can not be valid image disks (e.g. XML, PDF, TXT are
        # raw files), so it's necessary check the 'content' of them
        isvalid = True
        if fmt == 'raw':
            try:
                ms = magic.open(magic.NONE)
                ms.load()
                if ms.file(path).lower() not in VALID_RAW_CONTENT:
                    isvalid = False
                ms.close()
            except UnicodeDecodeError:
                isvalid = False

        used_by = get_disk_used_by(self.objstore, self.conn, path)
        res = dict(type=VOLUME_TYPE_MAP[info[0]],
                   capacity=info[1],
                   allocation=info[2],
                   path=path,
                   used_by=used_by,
                   format=fmt,
                   isvalid=isvalid)
        if fmt == 'iso':
            if os.path.islink(path):
                path = os.path.join(os.path.dirname(path), os.readlink(path))
            os_distro = os_version = 'unknown'
            try:
                if iso_img is None:
                    iso_img = IsoImage(path)
                os_distro, os_version = iso_img.probe()
                bootable = True
            except IsoFormatError:
                bootable = False
            res.update(
                dict(os_distro=os_distro, os_version=os_version, path=path,
                     bootable=bootable))
        return res

    def wipe(self, pool, name):
        volume = StorageVolumeModel.get_storagevolume(pool, name, self.conn)
        try:
            volume.wipePattern(libvirt.VIR_STORAGE_VOL_WIPE_ALG_ZERO, 0)
        except libvirt.libvirtError as e:
            raise OperationFailed("KCHVOL0009E",
                                  {'name': name, 'err': e.get_error_message()})

    def delete(self, pool, name):
        pool_info = StoragePoolModel(conn=self.conn,
                                     objstore=self.objstore).lookup(pool)
        if pool_info['type'] in READONLY_POOL_TYPE:
            raise InvalidParameter("KCHVOL0012E", {'type': pool_info['type']})

        volume = StorageVolumeModel.get_storagevolume(pool, name, self.conn)
        try:
            volume.delete(0)
        except libvirt.libvirtError as e:
            raise OperationFailed("KCHVOL0010E",
                                  {'name': name, 'err': e.get_error_message()})

    def resize(self, pool, name, size):
        volume = StorageVolumeModel.get_storagevolume(pool, name, self.conn)

        # When decreasing the storage volume capacity, the flag
        # VIR_STORAGE_VOL_RESIZE_SHRINK must be used
        flags = 0
        if volume.info()[1] > size:
            # FIXME: Even using VIR_STORAGE_VOL_RESIZE_SHRINK flag it is not
            # possible to decrease the volume capacity due a libvirt bug
            # For reference:
            # - https://bugzilla.redhat.com/show_bug.cgi?id=1021802
            flags = libvirt.VIR_STORAGE_VOL_RESIZE_SHRINK

        try:
            volume.resize(size, flags)
        except libvirt.libvirtError as e:
            raise OperationFailed("KCHVOL0011E",
                                  {'name': name, 'err': e.get_error_message()})

    def clone(self, pool, name, new_pool=None, new_name=None):
        """Clone a storage volume.

        Arguments:
        pool -- The name of the original pool.
        name -- The name of the original volume.
        new_pool -- The name of the destination pool (optional). If omitted,
            the new volume will be created on the same pool as the
            original one.
        new_name -- The name of the new volume (optional). If omitted, a new
            value based on the original volume's name will be used.

        Return:
        A Task running the clone operation.
        """
        # the same pool will be used if no pool is specified
        if new_pool is None:
            new_pool = pool

        # a default name based on the original name will be used if no name
        # is specified
        if new_name is None:
            base, ext = os.path.splitext(name)
            new_name = get_next_clone_name(self.storagevolumes.get_list(pool),
                                           base, ext)

        params = {'pool': pool,
                  'name': name,
                  'new_pool': new_pool,
                  'new_name': new_name}
        taskid = add_task(u'/plugins/kimchi/storagepools/%s/storagevolumes/%s'
                          % (pool, new_name), self._clone_task, self.objstore,
                          params)
        return self.task.lookup(taskid)

    def _clone_task(self, cb, params):
        """Asynchronous function which performs the clone operation.

        This function copies all the data inside the original volume into the
        new one.

        Arguments:
        cb -- A callback function to signal the Task's progress.
        params -- A dict with the following values:
            "pool": The name of the original pool.
            "name": The name of the original volume.
            "new_pool": The name of the destination pool.
            "new_name": The name of the new volume.
        """
        orig_pool_name = params['pool']
        orig_vol_name = params['name']
        new_pool_name = params['new_pool']
        new_vol_name = params['new_name']

        try:
            cb('setting up volume cloning')
            orig_vir_vol = StorageVolumeModel.get_storagevolume(orig_pool_name,
                                                                orig_vol_name,
                                                                self.conn)
            orig_vol = self.lookup(orig_pool_name, orig_vol_name)
            new_vir_pool = StoragePoolModel.get_storagepool(new_pool_name,
                                                            self.conn)

            cb('building volume XML')
            root_elem = E.volume()
            root_elem.append(E.name(new_vol_name))
            root_elem.append(E.capacity(unicode(orig_vol['capacity']),
                                        unit='bytes'))
            target_elem = E.target()
            target_elem.append(E.format(type=orig_vol['format']))
            root_elem.append(target_elem)
            new_vol_xml = ET.tostring(root_elem, encoding='utf-8',
                                      pretty_print=True)

            cb('cloning volume')
            new_vir_pool.createXMLFrom(new_vol_xml, orig_vir_vol, 0)
        except (InvalidOperation, NotFoundError, libvirt.libvirtError), e:
            raise OperationFailed('KCHVOL0023E',
                                  {'name': orig_vol_name,
                                   'pool': orig_pool_name,
                                   'err': e.get_error_message()})

        new_vol = self.lookup(new_pool_name, new_vol_name)
        cb('adding volume to the object store')
        set_disk_used_by(self.objstore, new_vol['path'], [])

        cb('OK', True)
Пример #7
0
                        virt_stream.abort()
                    if virt_vol:
                        virt_vol.delete(0)
                except libvirt.libvirtError, virt_e:
                    wok_log.error(virt_e.message)
                finally:
                    raise OperationFailed('KCHVOL0007E', {'name': name,
                                                          'pool': pool_name,
                                                          'err': e.message})
            finally:
                os.remove(file_path)

        vol_info = StorageVolumeModel(conn=self.conn,
                                      objstore=self.objstore).lookup(pool_name,
                                                                     name)
        set_disk_used_by(self.objstore, vol_info['path'], [])

        cb('OK', True)

    def get_list(self, pool_name):
        pool = StoragePoolModel.get_storagepool(pool_name, self.conn)
        if not pool.isActive():
            raise InvalidOperation("KCHVOL0006E", {'pool': pool_name})
        try:
            pool.refresh(0)
            return sorted(map(lambda x: x.decode('utf-8'), pool.listVolumes()))
        except libvirt.libvirtError as e:
            raise OperationFailed("KCHVOL0008E",
                                  {'pool': pool_name,
                                   'err': e.get_error_message()})
Пример #8
0
    def create(self, vm_name, params):
        vol_model = None
        # Path will never be blank due to API.json verification.
        # There is no need to cover this case here.
        if not ('vol' in params) ^ ('path' in params):
            raise InvalidParameter("KCHVMSTOR0017E")

        dom = VMModel.get_vm(vm_name, self.conn)
        params['bus'] = _get_device_bus(params['type'], dom)
        params['format'] = 'raw'

        dev_list = [
            dev for dev, bus in get_vm_disks(dom).iteritems()
            if bus == params['bus']
        ]
        dev_list.sort()
        if len(dev_list) == 0:
            params['index'] = 0
        else:
            char = dev_list.pop()[2]
            params['index'] = string.ascii_lowercase.index(char) + 1

        if (params['bus'] not in HOTPLUG_TYPE
                and DOM_STATE_MAP[dom.info()[0]] != 'shutoff'):
            raise InvalidOperation('KCHVMSTOR0011E')

        if params.get('vol'):
            try:
                pool = params['pool']
                vol_model = StorageVolumeModel(conn=self.conn,
                                               objstore=self.objstore)
                vol_info = vol_model.lookup(pool, params['vol'])
            except KeyError:
                raise InvalidParameter("KCHVMSTOR0012E")
            except Exception as e:
                raise InvalidParameter("KCHVMSTOR0015E", {'error': e})
            if len(vol_info['used_by']) != 0:
                raise InvalidParameter("KCHVMSTOR0016E")

            valid_format = {
                "disk": ["raw", "bochs", "qcow", "qcow2", "qed", "vmdk"],
                "cdrom": "iso"
            }

            if vol_info['type'] == 'file':
                if (params['type'] == 'disk' and vol_info['format']
                        in valid_format[params['type']]):
                    params['format'] = vol_info['format']
                else:
                    raise InvalidParameter("KCHVMSTOR0018E", {
                        "format": vol_info['format'],
                        "type": params['type']
                    })

            if (params['format'] == 'raw' and not vol_info['isvalid']):
                message = 'This is not a valid RAW disk image.'
                raise OperationFailed('KCHVMSTOR0008E', {'error': message})

            params['path'] = vol_info['path']
            params['disk'] = vol_info['type']

        params.update(self._get_available_bus_address(params['bus'], vm_name))

        # Add device to VM
        dev, xml = get_disk_xml(params)
        try:
            dom = VMModel.get_vm(vm_name, self.conn)
            dom.attachDeviceFlags(xml, get_vm_config_flag(dom, 'all'))
        except Exception as e:
            raise OperationFailed("KCHVMSTOR0008E", {'error': e.message})

        # Don't put a try-block here. Let the exception be raised. If we
        #   allow disks used_by to be out of sync, data corruption could
        #   occour if a disk is added to two guests unknowingly.
        if params.get('vol'):
            used_by = vol_info['used_by']
            used_by.append(vm_name)
            set_disk_used_by(self.objstore, params['path'], used_by)

        return dev
Пример #9
0
    def create(self, vm_name, params):
        vol_model = None
        # Path will never be blank due to API.json verification.
        # There is no need to cover this case here.
        if not ('vol' in params) ^ ('path' in params):
            raise InvalidParameter("KCHVMSTOR0017E")

        dom = VMModel.get_vm(vm_name, self.conn)
        params['bus'] = _get_device_bus(params['type'], dom)
        params['format'] = 'raw'

        dev_list = [dev for dev, bus in get_vm_disks(dom).iteritems()
                    if bus == params['bus']]
        dev_list.sort()
        if len(dev_list) == 0:
            params['index'] = 0
        else:
            char = dev_list.pop()[2]
            params['index'] = string.ascii_lowercase.index(char) + 1

        if (params['bus'] not in HOTPLUG_TYPE and
           DOM_STATE_MAP[dom.info()[0]] != 'shutoff'):
            raise InvalidOperation('KCHVMSTOR0011E')

        if params.get('vol'):
            try:
                pool = params['pool']
                vol_model = StorageVolumeModel(conn=self.conn,
                                               objstore=self.objstore)
                vol_info = vol_model.lookup(pool, params['vol'])
            except KeyError:
                raise InvalidParameter("KCHVMSTOR0012E")
            except Exception as e:
                raise InvalidParameter("KCHVMSTOR0015E", {'error': e})
            if len(vol_info['used_by']) != 0:
                raise InvalidParameter("KCHVMSTOR0016E")

            valid_format = {
                "disk": ["raw", "bochs", "qcow", "qcow2", "qed", "vmdk"],
                "cdrom": "iso"}

            if vol_info['type'] == 'file':
                if (params['type'] == 'disk' and
                        vol_info['format'] in valid_format[params['type']]):
                    params['format'] = vol_info['format']
                else:
                    raise InvalidParameter("KCHVMSTOR0018E",
                                           {"format": vol_info['format'],
                                            "type": params['type']})

            if (params['format'] == 'raw' and not vol_info['isvalid']):
                message = 'This is not a valid RAW disk image.'
                raise OperationFailed('KCHVMSTOR0008E', {'error': message})

            params['path'] = vol_info['path']
            params['disk'] = vol_info['type']

        params.update(self._get_available_bus_address(params['bus'], vm_name))

        # Add device to VM
        dev, xml = get_disk_xml(params)
        try:
            dom = VMModel.get_vm(vm_name, self.conn)
            dom.attachDeviceFlags(xml, get_vm_config_flag(dom, 'all'))
        except Exception as e:
            raise OperationFailed("KCHVMSTOR0008E", {'error': e.message})

        # Don't put a try-block here. Let the exception be raised. If we
        #   allow disks used_by to be out of sync, data corruption could
        #   occour if a disk is added to two guests unknowingly.
        if params.get('vol'):
            used_by = vol_info['used_by']
            used_by.append(vm_name)
            set_disk_used_by(self.objstore, params['path'], used_by)

        return dev