2013-03-18 01:06:52 +04:00
#
2015-03-31 18:59:06 +03:00
# Copyright 2008, 2013, 2015 Red Hat, Inc.
2013-03-18 01:06:52 +04:00
# Cole Robinson <crobinso@redhat.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
2013-10-28 00:59:47 +04:00
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
2013-03-18 01:06:52 +04:00
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA.
import os
import threading
import time
import logging
import libvirt
2014-09-12 23:59:22 +04:00
from . xmlbuilder import XMLBuilder , XMLChildProperty , XMLProperty
from . import util
2013-03-18 01:06:52 +04:00
2015-05-03 02:54:14 +03:00
_DEFAULT_DEV_TARGET = " /dev "
_DEFAULT_LVM_TARGET_BASE = " /dev/ "
_DEFAULT_SCSI_TARGET = " /dev/disk/by-path "
_DEFAULT_MPATH_TARGET = " /dev/mapper "
2013-03-18 01:06:52 +04:00
2013-09-20 04:18:12 +04:00
class _StoragePermissions ( XMLBuilder ) :
_XML_ROOT_NAME = " permissions "
_XML_PROP_ORDER = [ " mode " , " owner " , " group " , " label " ]
2013-03-18 01:06:52 +04:00
2013-09-20 04:18:12 +04:00
mode = XMLProperty ( " ./mode " )
owner = XMLProperty ( " ./owner " )
group = XMLProperty ( " ./group " )
label = XMLProperty ( " ./label " )
2013-03-18 01:06:52 +04:00
2013-09-20 04:18:12 +04:00
class _StorageObject ( XMLBuilder ) :
2013-03-18 01:06:52 +04:00
"""
Base class for building any libvirt storage object .
2013-09-20 04:18:12 +04:00
Meaningless to directly instantiate .
2013-03-18 01:06:52 +04:00
"""
2013-09-20 04:18:12 +04:00
######################
# Validation helpers #
######################
2013-03-18 01:06:52 +04:00
def _check_name_collision ( self , name ) :
raise NotImplementedError ( )
2013-09-20 04:18:12 +04:00
def _validate_name ( self , name ) :
if name == self . name :
return
util . validate_name ( _ ( " Storage object " ) , name )
self . _check_name_collision ( name )
return name
2013-03-18 01:06:52 +04:00
2013-09-20 04:18:12 +04:00
##############
# Properties #
##############
2013-03-18 01:06:52 +04:00
2013-09-20 04:18:12 +04:00
name = XMLProperty ( " ./name " , validate_cb = _validate_name ,
doc = _ ( " Name for the storage object. " ) )
permissions = XMLChildProperty ( _StoragePermissions ,
relative_xpath = " ./target " ,
is_single = True )
2013-03-18 01:06:52 +04:00
2014-02-06 04:09:26 +04:00
def _get_default_pool_path ( conn ) :
path = " /var/lib/libvirt/images "
if conn . is_session_uri ( ) :
2015-08-10 19:35:13 +03:00
path = os . path . expanduser ( " ~/.local/share/libvirt/images " )
2014-02-06 04:09:26 +04:00
return path
2014-12-10 00:05:02 +03:00
class _Host ( XMLBuilder ) :
_XML_PROP_ORDER = [ " name " , " port " ]
_XML_ROOT_NAME = " host "
name = XMLProperty ( " ./@name " )
port = XMLProperty ( " ./@port " , is_int = True )
2013-09-20 04:18:12 +04:00
class StoragePool ( _StorageObject ) :
2013-03-18 01:06:52 +04:00
"""
Base class for building and installing libvirt storage pool xml
"""
2013-04-12 16:26:21 +04:00
# @group Types: TYPE_*
2013-03-18 01:06:52 +04:00
TYPE_DIR = " dir "
TYPE_FS = " fs "
TYPE_NETFS = " netfs "
TYPE_LOGICAL = " logical "
TYPE_DISK = " disk "
TYPE_ISCSI = " iscsi "
TYPE_SCSI = " scsi "
TYPE_MPATH = " mpath "
2014-02-12 13:39:50 +04:00
TYPE_GLUSTER = " gluster "
2014-12-10 00:05:02 +03:00
TYPE_RBD = " rbd "
TYPE_SHEEPDOG = " sheepdog "
2016-08-09 18:48:34 +03:00
TYPE_ZFS = " zfs "
2013-03-18 01:06:52 +04:00
# Pool type descriptions for use in higher level programs
2013-09-20 04:18:12 +04:00
_descs = { }
_descs [ TYPE_DIR ] = _ ( " Filesystem Directory " )
_descs [ TYPE_FS ] = _ ( " Pre-Formatted Block Device " )
_descs [ TYPE_NETFS ] = _ ( " Network Exported Directory " )
_descs [ TYPE_LOGICAL ] = _ ( " LVM Volume Group " )
_descs [ TYPE_DISK ] = _ ( " Physical Disk Device " )
_descs [ TYPE_ISCSI ] = _ ( " iSCSI Target " )
_descs [ TYPE_SCSI ] = _ ( " SCSI Host Adapter " )
_descs [ TYPE_MPATH ] = _ ( " Multipath Device Enumerator " )
2014-02-12 13:39:50 +04:00
_descs [ TYPE_GLUSTER ] = _ ( " Gluster Filesystem " )
2014-12-10 00:05:02 +03:00
_descs [ TYPE_RBD ] = _ ( " RADOS Block Device/Ceph " )
_descs [ TYPE_SHEEPDOG ] = _ ( " Sheepdog Filesystem " )
2016-08-09 18:48:34 +03:00
_descs [ TYPE_ZFS ] = _ ( " ZFS Pool " )
2013-09-20 04:18:12 +04:00
@staticmethod
def get_pool_types ( ) :
2013-03-18 01:06:52 +04:00
"""
2013-09-20 04:18:12 +04:00
Return list of appropriate pool types
2013-03-18 01:06:52 +04:00
"""
2013-09-20 04:18:12 +04:00
return StoragePool . _descs . keys ( )
2013-03-18 01:06:52 +04:00
2013-09-20 04:18:12 +04:00
@staticmethod
2013-03-18 01:06:52 +04:00
def get_pool_type_desc ( pool_type ) :
2013-09-20 04:18:12 +04:00
"""
Return human readable description for passed pool type
"""
return StoragePool . _descs . get ( pool_type , " %s pool " % pool_type )
2013-03-18 01:06:52 +04:00
2013-09-20 04:18:12 +04:00
@staticmethod
def pool_list_from_sources ( conn , pool_type , host = None ) :
2013-03-18 01:06:52 +04:00
"""
Return a list of StoragePool instances built from libvirt ' s pool
source enumeration ( if supported ) .
@param conn : Libvirt connection
@param name : Name for the new pool
@param pool_type : Pool type string from I { Types }
@param host : Option host string to poll for sources
"""
2013-10-06 18:08:04 +04:00
if not conn . check_support ( conn . SUPPORT_CONN_FINDPOOLSOURCES ) :
2013-03-18 01:06:52 +04:00
return [ ]
if host :
source_xml = " <source><host name= ' %s ' /></source> " % host
else :
source_xml = " <source/> "
try :
xml = conn . findStoragePoolSources ( pool_type , source_xml , 0 )
2017-05-05 19:47:21 +03:00
except libvirt . libvirtError as e :
2013-07-06 19:20:28 +04:00
if util . is_error_nosupport ( e ) :
2013-03-18 01:06:52 +04:00
return [ ]
raise
2015-04-03 23:22:25 +03:00
class _EnumerateSource ( XMLBuilder ) :
_XML_ROOT_NAME = " source "
class _EnumerateSources ( XMLBuilder ) :
_XML_ROOT_NAME = " sources "
sources = XMLChildProperty ( _EnumerateSource )
ret = [ ]
sources = _EnumerateSources ( conn , xml )
for source in sources . sources :
source_xml = source . get_xml_config ( )
2016-08-24 22:55:08 +03:00
pool_xml = " <pool> \n %s \n </pool> " % (
XMLBuilder . xml_indent ( source_xml , 2 ) )
2015-04-03 23:22:25 +03:00
parseobj = StoragePool ( conn , parsexml = pool_xml )
parseobj . type = pool_type
obj = StoragePool ( conn )
obj . type = pool_type
obj . source_path = parseobj . source_path
2017-06-15 15:18:26 +03:00
for h in parseobj . hosts :
parseobj . remove_host ( h )
obj . add_host_obj ( h )
2015-04-03 23:22:25 +03:00
obj . source_name = parseobj . source_name
obj . format = parseobj . format
ret . append ( obj )
return ret
2013-09-20 04:18:12 +04:00
@staticmethod
def build_default_pool ( conn ) :
"""
Helper to build the ' default ' storage pool
"""
2013-10-06 18:08:04 +04:00
if not conn . check_support ( conn . SUPPORT_CONN_STORAGE ) :
2013-09-20 04:18:12 +04:00
return
pool = None
name = " default "
2014-02-06 04:09:26 +04:00
path = _get_default_pool_path ( conn )
2015-05-03 02:09:14 +03:00
if conn . is_session_uri ( ) and not os . path . exists ( path ) :
os . makedirs ( path )
2013-09-20 04:18:12 +04:00
try :
pool = conn . storagePoolLookupByName ( name )
except libvirt . libvirtError :
2015-08-13 01:00:14 +03:00
# Try default pool path when "default" name fails
pool = StoragePool . lookup_pool_by_path ( conn , path )
2013-09-20 04:18:12 +04:00
if pool :
2015-08-13 01:00:14 +03:00
# This is a libvirt pool object so create a StoragePool from it
return StoragePool ( conn , parsexml = pool . XMLDesc ( 0 ) )
2013-09-20 04:18:12 +04:00
try :
logging . debug ( " Attempting to build default pool with target ' %s ' " ,
path )
defpool = StoragePool ( conn )
defpool . type = defpool . TYPE_DIR
defpool . name = name
defpool . target_path = path
2014-02-06 04:09:26 +04:00
defpool . install ( build = True , create = True , autostart = True )
return defpool
2017-05-05 19:47:21 +03:00
except Exception as e :
2013-09-20 04:18:12 +04:00
raise RuntimeError (
_ ( " Couldn ' t create default storage pool ' %s ' : %s " ) %
( path , str ( e ) ) )
2016-06-17 03:36:30 +03:00
@staticmethod
def manage_path ( conn , path ) :
"""
If the passed path is managed , lookup its storage objects .
If the passed path isn ' t managed, attempt to manage it if
we can .
: returns : ( vol , parent pool ) tuple
"""
from . import diskbackend
return diskbackend . manage_path ( conn , path )
2014-02-06 04:09:26 +04:00
@staticmethod
2015-05-03 02:33:16 +03:00
def get_default_dir ( conn , build = False ) :
2014-02-06 04:09:26 +04:00
"""
2015-05-03 02:33:16 +03:00
Return the default storage dir . If there ' s a ' default ' pool,
report that . If there ' s no default pool, return the dir we would
2014-02-06 04:09:26 +04:00
use for the default .
"""
path = _get_default_pool_path ( conn )
2015-05-03 02:33:16 +03:00
if ( not conn . is_remote ( ) and
not conn . check_support ( conn . SUPPORT_CONN_STORAGE ) ) :
if build and not os . path . exists ( path ) :
os . makedirs ( path )
2014-02-06 04:09:26 +04:00
return path
try :
2015-05-03 02:33:16 +03:00
for pool in conn . fetch_all_pools ( ) :
if pool . name == " default " :
return pool . target_path
2017-07-24 11:26:48 +03:00
except Exception :
2014-02-06 04:09:26 +04:00
pass
2014-06-01 00:30:00 +04:00
if build :
return StoragePool . build_default_pool ( conn ) . target_path
return _get_default_pool_path ( conn )
2014-02-06 04:09:26 +04:00
2013-09-23 01:34:53 +04:00
@staticmethod
2014-12-05 03:52:32 +03:00
def lookup_pool_by_path ( conn , path ) :
2013-09-23 01:34:53 +04:00
"""
Return the first pool with matching matching target path .
return the first we find , active or inactive . This iterates over
all pools and dumps their xml , so it is NOT quick .
2014-09-12 21:04:22 +04:00
2013-09-23 01:34:53 +04:00
@returns : virStoragePool object if found , None otherwise
"""
2013-10-06 18:08:04 +04:00
if not conn . check_support ( conn . SUPPORT_CONN_STORAGE ) :
2013-09-23 01:34:53 +04:00
return None
for pool in conn . fetch_all_pools ( ) :
2014-12-05 03:52:32 +03:00
xml_path = pool . target_path
if xml_path is not None and os . path . abspath ( xml_path ) == path :
2013-09-29 04:05:13 +04:00
return conn . storagePoolLookupByName ( pool . name )
2013-09-23 01:34:53 +04:00
return None
2014-02-09 01:36:45 +04:00
@staticmethod
def find_free_name ( conn , basename , * * kwargs ) :
"""
Finds a name similar ( or equal ) to passed ' basename ' that is not
in use by another pool . Extra params are passed to generate_name
"""
2017-07-19 02:09:58 +03:00
def cb ( name ) :
for pool in conn . fetch_all_pools ( ) :
if pool . name == name :
return True
return False
kwargs [ " lib_collision " ] = False
return util . generate_name ( basename , cb , * * kwargs )
2013-09-23 01:34:53 +04:00
2013-09-20 04:18:12 +04:00
######################
# Validation helpers #
######################
2013-03-18 01:06:52 +04:00
def _check_name_collision ( self , name ) :
pool = None
try :
pool = self . conn . storagePoolLookupByName ( name )
except libvirt . libvirtError :
pass
if pool :
raise ValueError ( _ ( " Name ' %s ' already in use by another pool. " %
name ) )
def _get_default_target_path ( self ) :
2014-02-12 13:29:22 +04:00
if not self . supports_property ( " target_path " ) :
return None
2013-09-20 04:18:12 +04:00
if ( self . type == self . TYPE_DIR or
self . type == self . TYPE_NETFS or
self . type == self . TYPE_FS ) :
2015-05-03 02:54:14 +03:00
return os . path . join ( self . get_default_dir ( self . conn ) , self . name )
2013-09-20 04:18:12 +04:00
if self . type == self . TYPE_LOGICAL :
name = self . name
if self . source_name :
name = self . source_name
2015-05-03 02:54:14 +03:00
return _DEFAULT_LVM_TARGET_BASE + name
2013-09-20 04:18:12 +04:00
if self . type == self . TYPE_DISK :
2015-05-03 02:54:14 +03:00
return _DEFAULT_DEV_TARGET
2013-09-20 04:18:12 +04:00
if self . type == self . TYPE_ISCSI or self . type == self . TYPE_SCSI :
2015-05-03 02:54:14 +03:00
return _DEFAULT_SCSI_TARGET
2013-09-20 04:18:12 +04:00
if self . type == self . TYPE_MPATH :
2015-05-03 02:54:14 +03:00
return _DEFAULT_MPATH_TARGET
2013-09-20 04:18:12 +04:00
raise RuntimeError ( " No default target_path for type= %s " % self . type )
2015-05-06 20:54:00 +03:00
def _type_to_source_prop ( self ) :
2014-12-10 00:05:02 +03:00
if ( self . type == self . TYPE_NETFS or
self . type == self . TYPE_GLUSTER ) :
2015-05-06 20:54:00 +03:00
return " _source_dir "
elif self . type == self . TYPE_SCSI :
return " _source_adapter "
else :
return " _source_device "
def _get_source ( self ) :
return getattr ( self , self . _type_to_source_prop ( ) )
def _set_source ( self , val ) :
return setattr ( self , self . _type_to_source_prop ( ) , val )
source_path = property ( _get_source , _set_source )
2013-09-20 04:18:12 +04:00
def _default_source_name ( self ) :
2017-08-03 19:38:37 +03:00
srcname = None
2013-03-18 01:06:52 +04:00
2017-08-03 19:38:37 +03:00
if not self . supports_property ( " source_name " ) :
srcname = None
elif self . type == StoragePool . TYPE_NETFS :
2014-12-10 00:05:02 +03:00
srcname = self . name
elif self . type == StoragePool . TYPE_RBD :
srcname = " rbd "
elif self . type == StoragePool . TYPE_GLUSTER :
srcname = " gv0 "
elif ( " target_path " in self . _propstore and
2017-09-20 10:36:27 +03:00
self . target_path and
self . target_path . startswith ( _DEFAULT_LVM_TARGET_BASE ) ) :
2013-09-20 04:18:12 +04:00
# If there is a target path, parse it for an expected VG
# location, and pull the name from there
2015-05-03 02:54:14 +03:00
vg = self . target_path [ len ( _DEFAULT_LVM_TARGET_BASE ) : ]
2013-09-20 04:18:12 +04:00
srcname = vg . split ( " / " , 1 ) [ 0 ]
2013-03-18 01:06:52 +04:00
2013-09-20 04:18:12 +04:00
return srcname
def _default_format_cb ( self ) :
if not self . supports_property ( " format " ) :
return None
return " auto "
##############
# Properties #
##############
_XML_ROOT_NAME = " pool "
_XML_PROP_ORDER = [ " name " , " type " , " uuid " ,
" capacity " , " allocation " , " available " ,
2014-12-10 00:05:02 +03:00
" format " , " hosts " ,
2015-05-06 20:54:00 +03:00
" _source_dir " , " _source_adapter " , " _source_device " ,
" source_name " , " target_path " ,
2014-12-10 00:05:02 +03:00
" permissions " ]
2013-09-20 04:18:12 +04:00
2015-05-06 20:54:00 +03:00
_source_dir = XMLProperty ( " ./source/dir/@path " )
_source_adapter = XMLProperty ( " ./source/adapter/@name " )
_source_device = XMLProperty ( " ./source/device/@path " )
2013-09-20 04:18:12 +04:00
type = XMLProperty ( " ./@type " ,
doc = _ ( " Storage device type the pool will represent. " ) )
2017-12-14 20:12:35 +03:00
uuid = XMLProperty ( " ./uuid " )
2013-09-20 04:18:12 +04:00
capacity = XMLProperty ( " ./capacity " , is_int = True )
allocation = XMLProperty ( " ./allocation " , is_int = True )
available = XMLProperty ( " ./available " , is_int = True )
format = XMLProperty ( " ./source/format/@type " ,
default_cb = _default_format_cb )
iqn = XMLProperty ( " ./source/initiator/iqn/@name " ,
doc = _ ( " iSCSI initiator qualified name " ) )
source_name = XMLProperty ( " ./source/name " ,
default_cb = _default_source_name ,
doc = _ ( " Name of the Volume Group " ) )
target_path = XMLProperty ( " ./target/path " ,
default_cb = _get_default_target_path )
2014-12-10 00:05:02 +03:00
def add_host_obj ( self , obj ) :
2015-09-04 22:45:45 +03:00
self . add_child ( obj )
2014-12-10 00:05:02 +03:00
def add_host ( self , name , port = None ) :
obj = _Host ( self . conn )
obj . name = name
obj . port = port
2015-09-04 22:45:45 +03:00
self . add_child ( obj )
2014-12-10 00:05:02 +03:00
def remove_host ( self , obj ) :
2015-09-04 22:45:45 +03:00
self . remove_child ( obj )
2014-12-10 00:05:02 +03:00
hosts = XMLChildProperty ( _Host , relative_xpath = " ./source " )
2013-09-20 04:18:12 +04:00
######################
# Public API helpers #
######################
def supports_property ( self , propname ) :
users = {
" source_path " : [ self . TYPE_FS , self . TYPE_NETFS , self . TYPE_LOGICAL ,
2014-12-10 00:05:02 +03:00
self . TYPE_DISK , self . TYPE_ISCSI , self . TYPE_SCSI ,
self . TYPE_GLUSTER ] ,
" source_name " : [ self . TYPE_LOGICAL , self . TYPE_GLUSTER ,
2016-08-09 18:48:34 +03:00
self . TYPE_RBD , self . TYPE_SHEEPDOG , self . TYPE_ZFS ] ,
2014-12-10 00:05:02 +03:00
" hosts " : [ self . TYPE_NETFS , self . TYPE_ISCSI , self . TYPE_GLUSTER ,
self . TYPE_RBD , self . TYPE_SHEEPDOG ] ,
2013-09-20 04:18:12 +04:00
" format " : [ self . TYPE_FS , self . TYPE_NETFS , self . TYPE_DISK ] ,
" iqn " : [ self . TYPE_ISCSI ] ,
2017-08-05 09:39:32 +03:00
" target_path " : [ self . TYPE_DIR , self . TYPE_FS , self . TYPE_NETFS ,
2014-02-12 13:29:22 +04:00
self . TYPE_LOGICAL , self . TYPE_DISK , self . TYPE_ISCSI ,
self . TYPE_SCSI , self . TYPE_MPATH ]
2013-09-20 04:18:12 +04:00
}
if users . get ( propname ) :
return self . type in users [ propname ]
return hasattr ( self , propname )
def list_formats ( self ) :
if self . type == self . TYPE_FS :
return [ " auto " , " ext2 " , " ext3 " , " ext4 " , " ufs " , " iso9660 " , " udf " ,
" gfs " , " gfs2 " , " vfat " , " hfs+ " , " xfs " ]
if self . type == self . TYPE_NETFS :
return [ " auto " , " nfs " , " glusterfs " ]
if self . type == self . TYPE_DISK :
return [ " auto " , " bsd " , " dos " , " dvh " , " gpt " , " mac " , " pc98 " , " sun " ]
return [ ]
def supports_volume_creation ( self ) :
return self . type in [
StoragePool . TYPE_DIR , StoragePool . TYPE_FS ,
StoragePool . TYPE_NETFS , StoragePool . TYPE_LOGICAL ,
2014-12-10 00:05:02 +03:00
StoragePool . TYPE_DISK ,
2016-08-09 18:48:34 +03:00
StoragePool . TYPE_RBD , StoragePool . TYPE_SHEEPDOG ,
StoragePool . TYPE_ZFS ]
2013-09-20 04:18:12 +04:00
2017-03-06 11:28:48 +03:00
def get_disk_type ( self ) :
if ( self . type == StoragePool . TYPE_DISK or
self . type == StoragePool . TYPE_LOGICAL or
self . type == StoragePool . TYPE_SCSI or
self . type == StoragePool . TYPE_MPATH or
self . type == StoragePool . TYPE_ZFS ) :
return StorageVolume . TYPE_BLOCK
if ( self . type == StoragePool . TYPE_GLUSTER or
self . type == StoragePool . TYPE_RBD or
self . type == StoragePool . TYPE_ISCSI or
self . type == StoragePool . TYPE_SHEEPDOG ) :
return StorageVolume . TYPE_NETWORK
return StorageVolume . TYPE_FILE
2013-09-20 04:18:12 +04:00
##################
# Build routines #
##################
def validate ( self ) :
2014-12-10 00:05:02 +03:00
if self . supports_property ( " host " ) and not self . hosts :
2013-09-20 04:18:12 +04:00
raise RuntimeError ( _ ( " Hostname is required " ) )
if ( self . supports_property ( " source_path " ) and
2016-04-18 23:42:12 +03:00
self . type != self . TYPE_LOGICAL and
2013-09-20 04:18:12 +04:00
not self . source_path ) :
raise RuntimeError ( _ ( " Source path is required " ) )
2013-03-18 01:06:52 +04:00
2013-09-20 04:18:12 +04:00
if ( self . type == self . TYPE_DISK and self . format == " auto " ) :
# There is no explicit "auto" type for disk pools, but leaving out
# the format type seems to do the job for existing formatted disks
self . format = None
2013-03-18 01:06:52 +04:00
def install ( self , meter = None , create = False , build = False , autostart = False ) :
"""
Install storage pool xml .
"""
2013-09-20 04:18:12 +04:00
if ( self . type == self . TYPE_LOGICAL and
build and not self . source_path ) :
raise ValueError ( _ ( " Must explicitly specify source path if "
" building pool " ) )
if ( self . type == self . TYPE_DISK and
build and self . format == " auto " ) :
raise ValueError ( _ ( " Must explicitly specify disk format if "
" formatting disk device. " ) )
2013-03-18 01:06:52 +04:00
xml = self . get_xml_config ( )
logging . debug ( " Creating storage pool ' %s ' with xml: \n %s " ,
self . name , xml )
2015-09-06 21:26:50 +03:00
meter = util . ensure_meter ( meter )
2013-03-18 01:06:52 +04:00
try :
pool = self . conn . storagePoolDefineXML ( xml , 0 )
2017-05-05 19:47:21 +03:00
except Exception as e :
2015-06-02 15:21:58 +03:00
raise RuntimeError ( _ ( " Could not define storage pool: %s " ) % str ( e ) )
2013-03-18 01:06:52 +04:00
errmsg = None
if build :
try :
pool . build ( libvirt . VIR_STORAGE_POOL_BUILD_NEW )
2017-05-05 19:47:21 +03:00
except Exception as e :
2015-06-02 15:21:58 +03:00
errmsg = _ ( " Could not build storage pool: %s " ) % str ( e )
2013-03-18 01:06:52 +04:00
if create and not errmsg :
try :
pool . create ( 0 )
2017-05-05 19:47:21 +03:00
except Exception as e :
2015-06-02 15:21:58 +03:00
errmsg = _ ( " Could not start storage pool: %s " ) % str ( e )
2013-03-18 01:06:52 +04:00
if autostart and not errmsg :
try :
pool . setAutostart ( True )
2017-05-05 19:47:21 +03:00
except Exception as e :
2015-06-02 15:21:58 +03:00
errmsg = _ ( " Could not set pool autostart flag: %s " ) % str ( e )
2013-03-18 01:06:52 +04:00
if errmsg :
# Try and clean up the leftover pool
try :
pool . undefine ( )
2017-05-05 19:47:21 +03:00
except Exception as e :
2013-03-18 01:06:52 +04:00
logging . debug ( " Error cleaning up pool after failure: " +
" %s " % str ( e ) )
raise RuntimeError ( errmsg )
2017-07-19 23:11:17 +03:00
self . conn . cache_new_pool ( pool )
2017-05-19 15:26:49 +03:00
2013-03-18 01:06:52 +04:00
return pool
2013-09-20 04:18:12 +04:00
class StorageVolume ( _StorageObject ) :
2013-03-18 01:06:52 +04:00
"""
Base class for building and installing libvirt storage volume xml
"""
2015-09-29 20:25:34 +03:00
ALL_FORMATS = [ " raw " , " bochs " , " cloop " , " dmg " , " iso " , " qcow " ,
2014-01-24 14:40:09 +04:00
" qcow2 " , " qed " , " vmdk " , " vpc " , " fat " , " vhd " , " vdi " ]
2014-02-05 02:30:24 +04:00
@staticmethod
def get_file_extension_for_format ( fmt ) :
if not fmt :
return " "
if fmt == " raw " :
return " .img "
return " . " + fmt
2013-09-20 04:18:12 +04:00
@staticmethod
2013-10-05 18:22:27 +04:00
def find_free_name ( pool_object , basename , * * kwargs ) :
2013-03-18 01:06:52 +04:00
"""
2013-10-01 00:21:23 +04:00
Finds a name similar ( or equal ) to passed ' basename ' that is not
2014-02-09 01:36:45 +04:00
in use by another volume . Extra params are passed to generate_name
2013-03-18 01:06:52 +04:00
"""
pool_object . refresh ( 0 )
2013-10-05 18:22:27 +04:00
return util . generate_name ( basename ,
pool_object . storageVolLookupByName ,
* * kwargs )
2013-03-18 01:06:52 +04:00
2013-09-20 04:18:12 +04:00
TYPE_FILE = getattr ( libvirt , " VIR_STORAGE_VOL_FILE " , 0 )
TYPE_BLOCK = getattr ( libvirt , " VIR_STORAGE_VOL_BLOCK " , 1 )
2014-12-10 00:05:02 +03:00
TYPE_DIR = getattr ( libvirt , " VIR_STORAGE_VOL_DIR " , 2 )
2014-12-10 20:55:08 +03:00
TYPE_NETWORK = getattr ( libvirt , " VIR_STORAGE_VOL_NETWORK " , 3 )
TYPE_NETDIR = getattr ( libvirt , " VIR_STORAGE_VOL_NETDIR " , 4 )
2013-09-20 04:18:12 +04:00
def __init__ ( self , * args , * * kwargs ) :
_StorageObject . __init__ ( self , * args , * * kwargs )
self . _input_vol = None
self . _pool = None
2014-12-10 00:05:02 +03:00
self . _pool_xml = None
2015-02-07 05:18:05 +03:00
self . _reflink = False
2013-09-20 04:18:12 +04:00
# Indicate that the volume installation has finished. Used to
# definitively tell the storage progress thread to stop polling.
self . _install_finished = True
######################
# Non XML properties #
######################
def _get_pool ( self ) :
2013-03-18 01:06:52 +04:00
return self . _pool
2013-09-20 04:18:12 +04:00
def _set_pool ( self , newpool ) :
2013-03-18 01:06:52 +04:00
if newpool . info ( ) [ 0 ] != libvirt . VIR_STORAGE_POOL_RUNNING :
2015-06-02 15:21:58 +03:00
raise ValueError ( _ ( " pool ' %s ' must be active. " ) % newpool . name ( ) )
2013-03-18 01:06:52 +04:00
self . _pool = newpool
2014-12-10 00:05:02 +03:00
self . _pool_xml = StoragePool ( self . conn ,
parsexml = self . _pool . XMLDesc ( 0 ) )
2013-09-20 04:18:12 +04:00
pool = property ( _get_pool , _set_pool )
2013-03-18 01:06:52 +04:00
2013-09-20 04:18:12 +04:00
def _get_input_vol ( self ) :
2013-03-18 01:06:52 +04:00
return self . _input_vol
2013-09-20 04:18:12 +04:00
def _set_input_vol ( self , vol ) :
2013-03-18 01:06:52 +04:00
if vol is None :
self . _input_vol = None
return
if not isinstance ( vol , libvirt . virStorageVol ) :
raise ValueError ( _ ( " input_vol must be a virStorageVol " ) )
2013-04-12 17:51:26 +04:00
2013-10-06 18:08:04 +04:00
if not self . conn . check_support (
2017-09-20 10:36:27 +03:00
self . conn . SUPPORT_POOL_CREATEVOLFROM , self . pool ) :
2013-03-18 01:06:52 +04:00
raise ValueError ( _ ( " Creating storage from an existing volume is "
" not supported by this libvirt version. " ) )
2013-09-20 04:18:12 +04:00
2013-03-18 01:06:52 +04:00
self . _input_vol = vol
2013-09-20 04:18:12 +04:00
input_vol = property ( _get_input_vol , _set_input_vol ,
2013-03-18 01:06:52 +04:00
doc = _ ( " virStorageVolume pointer to clone/use as "
" input. " ) )
2015-02-07 05:18:05 +03:00
def _get_reflink ( self ) :
return self . _reflink
def _set_reflink ( self , reflink ) :
2015-02-18 22:49:04 +03:00
if ( reflink and not
self . conn . check_support ( self . conn . SUPPORT_POOL_REFLINK ) ) :
2015-02-07 05:18:05 +03:00
raise ValueError ( _ ( " Creating storage by btrfs COW copy is "
" not supported by this libvirt version. " ) )
self . _reflink = reflink
reflink = property ( _get_reflink , _set_reflink ,
doc = " flags for VIR_STORAGE_VOL_CREATE_REFLINK " )
2015-03-31 18:59:06 +03:00
def sync_input_vol ( self , only_format = False ) :
2014-07-28 12:55:28 +04:00
# Pull parameters from input vol into this class
2013-09-20 04:18:12 +04:00
parsevol = StorageVolume ( self . conn ,
parsexml = self . _input_vol . XMLDesc ( 0 ) )
2015-03-31 18:59:06 +03:00
self . format = parsevol . format
2015-04-10 13:59:20 +03:00
self . capacity = parsevol . capacity
self . allocation = parsevol . allocation
2015-03-31 18:59:06 +03:00
if only_format :
return
2013-09-20 04:18:12 +04:00
self . pool = self . _input_vol . storagePoolLookupByVolume ( )
##########################
# XML validation helpers #
##########################
2013-03-18 01:06:52 +04:00
def _check_name_collision ( self , name ) :
vol = None
try :
vol = self . pool . storageVolLookupByName ( name )
except libvirt . libvirtError :
pass
if vol :
raise ValueError ( _ ( " Name ' %s ' already in use by another volume. " %
name ) )
2013-09-20 04:18:12 +04:00
def _default_format ( self ) :
if self . file_type == self . TYPE_FILE :
return " raw "
return None
def _get_vol_type ( self ) :
2014-12-10 00:05:02 +03:00
if self . type :
2016-06-17 03:36:30 +03:00
if self . type == " file " :
return self . TYPE_FILE
elif self . type == " block " :
return self . TYPE_BLOCK
elif self . type == " dir " :
return self . TYPE_DIR
elif self . type == " network " :
return self . TYPE_NETWORK
2017-03-06 11:28:48 +03:00
return self . _pool_xml . get_disk_type ( )
2013-09-20 04:18:12 +04:00
file_type = property ( _get_vol_type )
##################
# XML properties #
##################
_XML_ROOT_NAME = " volume "
2013-12-05 18:17:12 +04:00
_XML_PROP_ORDER = [ " name " , " key " , " capacity " , " allocation " , " format " ,
2013-09-20 04:18:12 +04:00
" target_path " , " permissions " ]
2014-12-10 00:05:02 +03:00
type = XMLProperty ( " ./@type " )
2013-12-05 18:17:12 +04:00
key = XMLProperty ( " ./key " )
2017-03-17 19:00:03 +03:00
capacity = XMLProperty ( " ./capacity " , is_int = True )
allocation = XMLProperty ( " ./allocation " , is_int = True )
2013-09-20 04:18:12 +04:00
format = XMLProperty ( " ./target/format/@type " , default_cb = _default_format )
target_path = XMLProperty ( " ./target/path " )
2013-09-29 03:11:27 +04:00
backing_store = XMLProperty ( " ./backingStore/path " )
2016-06-17 03:08:53 +03:00
backing_format = XMLProperty ( " ./backingStore/format/@type " )
2013-03-18 01:06:52 +04:00
2014-02-03 23:52:23 +04:00
def _lazy_refcounts_default_cb ( self ) :
2014-02-08 23:38:42 +04:00
if self . format != " qcow2 " :
return False
2014-02-03 23:52:23 +04:00
return self . conn . check_support (
self . conn . SUPPORT_CONN_QCOW2_LAZY_REFCOUNTS )
lazy_refcounts = XMLProperty ( " ./target/features/lazy_refcounts " ,
is_bool = True , default_cb = _lazy_refcounts_default_cb )
2013-03-18 01:06:52 +04:00
2016-06-17 03:36:30 +03:00
def _detect_backing_store_format ( self ) :
logging . debug ( " Attempting to detect format for backing_store= %s " ,
self . backing_store )
vol , pool = StoragePool . manage_path ( self . conn , self . backing_store )
if not vol :
logging . debug ( " Didn ' t find any volume for backing_store " )
return None
# Only set backing format for volumes that support
# the 'format' parameter as we know it, like qcow2 etc.
volxml = StorageVolume ( self . conn , vol . XMLDesc ( 0 ) )
volxml . pool = pool
logging . debug ( " Found backing store volume XML: \n %s " ,
volxml . get_xml_config ( ) )
if volxml . supports_property ( " format " ) :
logging . debug ( " Returning format= %s " , volxml . format )
return volxml . format
logging . debug ( " backing_store volume doesn ' t appear to have "
" a file format we can specify, returning None " )
return None
2013-09-20 04:18:12 +04:00
######################
# Public API helpers #
######################
2014-12-10 00:05:02 +03:00
def _supports_format ( self ) :
if self . file_type == self . TYPE_FILE :
return True
if self . _pool_xml . type == StoragePool . TYPE_GLUSTER :
return True
return False
2013-09-20 04:18:12 +04:00
2014-12-10 00:05:02 +03:00
def supports_property ( self , propname ) :
if propname == " format " :
return self . _supports_format ( )
2013-09-20 04:18:12 +04:00
return hasattr ( self , propname )
def list_formats ( self ) :
2014-12-10 00:05:02 +03:00
if self . _supports_format ( ) :
2014-01-26 00:52:34 +04:00
return self . ALL_FORMATS
2013-09-20 04:18:12 +04:00
return [ ]
def list_create_formats ( self ) :
2014-12-10 00:05:02 +03:00
if self . _supports_format ( ) :
2015-09-29 20:25:34 +03:00
return [ " raw " , " qcow " , " qcow2 " , " qed " , " vmdk " , " vpc " , " vdi " ]
2013-09-20 04:18:12 +04:00
return None
##################
# Build routines #
##################
def validate ( self ) :
2014-12-10 00:05:02 +03:00
if self . _pool_xml . type == StoragePool . TYPE_LOGICAL :
2013-09-20 04:18:12 +04:00
if self . allocation != self . capacity :
2017-05-05 21:21:15 +03:00
logging . warning ( _ ( " Sparse logical volumes are not supported, "
2013-09-20 04:18:12 +04:00
" setting allocation equal to capacity " ) )
self . allocation = self . capacity
2013-03-18 01:06:52 +04:00
2017-03-17 19:00:03 +03:00
isfatal , errmsg = self . is_size_conflict ( )
if isfatal :
raise ValueError ( errmsg )
if errmsg :
2017-05-05 21:21:15 +03:00
logging . warning ( errmsg )
2017-03-17 19:00:03 +03:00
2013-03-18 01:06:52 +04:00
def install ( self , meter = None ) :
"""
Build and install storage volume from xml
"""
2016-06-17 03:36:30 +03:00
if self . backing_store and not self . backing_format :
self . backing_format = self . _detect_backing_store_format ( )
2013-03-18 01:06:52 +04:00
xml = self . get_xml_config ( )
logging . debug ( " Creating storage volume ' %s ' with xml: \n %s " ,
self . name , xml )
t = threading . Thread ( target = self . _progress_thread ,
name = " Checking storage allocation " ,
args = ( meter , ) )
t . setDaemon ( True )
2015-09-06 21:26:50 +03:00
meter = util . ensure_meter ( meter )
2013-03-18 01:06:52 +04:00
2013-10-01 22:29:58 +04:00
cloneflags = 0
createflags = 0
if ( self . format == " qcow2 " and
not self . backing_store and
2014-12-09 20:36:09 +03:00
not self . conn . is_really_test ( ) and
2013-10-06 18:08:04 +04:00
self . conn . check_support (
self . conn . SUPPORT_POOL_METADATA_PREALLOC , self . pool ) ) :
2013-10-01 22:29:58 +04:00
createflags | = libvirt . VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA
2015-02-07 05:18:05 +03:00
if self . reflink :
2015-02-22 00:28:19 +03:00
cloneflags | = getattr ( libvirt ,
" VIR_STORAGE_VOL_CREATE_REFLINK " , 1 )
2015-02-07 05:18:05 +03:00
2013-03-18 01:06:52 +04:00
try :
2013-06-09 23:40:25 +04:00
self . _install_finished = False
t . start ( )
meter . start ( size = self . capacity ,
text = _ ( " Allocating ' %s ' " ) % self . name )
if self . input_vol :
2013-10-01 22:29:58 +04:00
vol = self . pool . createXMLFrom ( xml , self . input_vol , cloneflags )
2013-06-09 23:40:25 +04:00
else :
2013-10-01 22:29:58 +04:00
logging . debug ( " Using vol create flags= %s " , createflags )
vol = self . pool . createXML ( xml , createflags )
2013-06-09 23:40:25 +04:00
2013-07-23 16:02:45 +04:00
self . _install_finished = True
t . join ( )
2013-06-09 23:40:25 +04:00
meter . end ( self . capacity )
logging . debug ( " Storage volume ' %s ' install complete. " ,
self . name )
return vol
2017-05-05 19:47:21 +03:00
except Exception as e :
2014-12-10 00:05:02 +03:00
logging . debug ( " Error creating storage volume " , exc_info = True )
2013-06-09 23:40:25 +04:00
raise RuntimeError ( " Couldn ' t create storage volume "
" ' %s ' : ' %s ' " % ( self . name , str ( e ) ) )
2013-03-18 01:06:52 +04:00
def _progress_thread ( self , meter ) :
vol = None
if not meter :
return
2015-11-19 16:48:25 +03:00
while True :
2013-03-18 01:06:52 +04:00
try :
2013-09-29 19:31:03 +04:00
if not vol :
vol = self . pool . storageVolLookupByName ( self . name )
vol . info ( )
2013-03-18 01:06:52 +04:00
break
2017-07-24 11:26:48 +03:00
except Exception :
2016-04-18 23:42:12 +03:00
if time : # pylint: disable=using-constant-test
2015-12-24 19:28:39 +03:00
# This 'if' check saves some noise from the test suite
time . sleep ( .2 )
2013-03-18 01:06:52 +04:00
if self . _install_finished :
break
2013-04-13 22:34:52 +04:00
if vol is None :
2013-03-18 01:06:52 +04:00
logging . debug ( " Couldn ' t lookup storage volume in prog thread. " )
return
while not self . _install_finished :
ignore , ignore , alloc = vol . info ( )
meter . update ( alloc )
2013-06-09 23:19:35 +04:00
time . sleep ( 1 )
2013-03-18 01:06:52 +04:00
2017-03-17 19:00:03 +03:00
def is_size_conflict ( self ) :
2013-03-18 01:06:52 +04:00
"""
Report if requested size exceeds its pool ' s available amount
@returns : 2 element tuple :
1. True if collision is fatal , false otherwise
2. String message if some collision was encountered .
@rtype : 2 element C { tuple } : ( C { bool } , C { str } )
"""
2013-09-20 04:18:12 +04:00
if not self . pool :
return ( False , " " )
2013-04-13 22:34:52 +04:00
# pool info is [pool state, capacity, allocation, available]
2013-03-18 01:06:52 +04:00
avail = self . pool . info ( ) [ 3 ]
2017-03-17 19:00:03 +03:00
if self . allocation > avail :
2013-03-18 01:06:52 +04:00
return ( True , _ ( " There is not enough free space on the storage "
" pool to create the volume. "
2016-07-26 10:34:20 +03:00
" ( %d M requested allocation > %d M available) " ) %
2017-10-11 14:35:55 +03:00
( ( self . allocation / / ( 1024 * 1024 ) ) ,
( avail / / ( 1024 * 1024 ) ) ) )
2017-03-17 19:00:03 +03:00
elif self . capacity > avail :
2013-03-18 01:06:52 +04:00
return ( False , _ ( " The requested volume capacity will exceed the "
" available pool space when the volume is fully "
" allocated. "
2016-07-26 10:34:20 +03:00
" ( %d M requested capacity > %d M available) " ) %
2017-10-11 14:35:55 +03:00
( ( self . capacity / / ( 1024 * 1024 ) ) ,
( avail / / ( 1024 * 1024 ) ) ) )
2013-03-18 01:06:52 +04:00
return ( False , " " )