def connect_volume(self, connection_properties): """Connect to a volume. :param connection_properties: The dictionary that describes all of the target volume attributes; it needs to contain the StorPool 'client_id' and the common 'volume' and 'access_mode' values. :type connection_properties: dict :returns: dict """ client_id = connection_properties.get('client_id', None) if client_id is None: raise exception.BrickException(_LE( 'Invalid StorPool connection data, no client ID specified.')) volume_id = connection_properties.get('volume', None) if volume_id is None: raise exception.BrickException(_LE( 'Invalid StorPool connection data, no volume ID specified.')) volume = self._attach.volumeName(volume_id) mode = connection_properties.get('access_mode', None) if mode is None or mode not in ('rw', 'ro'): raise exception.BrickException(_LE( 'Invalid access_mode specified in the connection data.')) req_id = 'brick-%s-%s' % (client_id, volume_id) self._attach.add(req_id, { 'volume': volume, 'type': 'brick', 'id': req_id, 'rights': 1 if mode == 'ro' else 2, 'volsnap': False }) self._attach.sync(req_id, None) return {'type': 'block', 'path': '/dev/storpool/' + volume}
def get_volume_paths(self, connection_properties): """Return the list of existing paths for a volume. The job of this method is to find out what paths in the system are associated with a volume as described by the connection_properties. :param connection_properties: The dictionary that describes all of the target volume attributes; it needs to contain 'volume' and 'device_path' values. :type connection_properties: dict """ volume_id = connection_properties.get('volume', None) if volume_id is None: raise exception.BrickException(_LE( 'Invalid StorPool connection data, no volume ID specified.')) volume = self._attach.volumeName(volume_id) path = '/dev/storpool/' + volume dpath = connection_properties.get('device_path', None) if dpath is not None and dpath != path: raise exception.BrickException(_LE( 'Internal error: StorPool volume path {path} does not ' 'match device path {dpath}'), {path: path, dpath: dpath}) return [path]
def __init__(self, message=None, **kwargs): self.kwargs = kwargs self.kwargs['message'] = message if 'code' not in self.kwargs: try: self.kwargs['code'] = self.code except AttributeError: pass for k, v in self.kwargs.items(): if isinstance(v, Exception): self.kwargs[k] = six.text_type(v) if self._should_format(): try: message = self.message % kwargs except Exception: # kwargs doesn't match a variable in the message # log the issue and the kwargs LOG.exception(_LE("Exception in string format operation. " "msg='%s'"), self.message) for name, value in kwargs.items(): LOG.error(_LE("%(name)s: %(value)s"), {'name': name, 'value': value}) # at least get the core message out if something happened message = self.message # Put the message in 'msg' so that we can access it. If we have it in # message it will be overshadowed by the class' message attribute self.msg = message super(BrickException, self).__init__(message)
def disconnect_volume(self, connection_properties, device_info): """Disconnect a volume from the local host. The connection_properties are the same as from connect_volume. The device_info is returned from connect_volume. :param connection_properties: The dictionary that describes all of the target volume attributes; it needs to contain the StorPool 'client_id' and the common 'volume' values. :type connection_properties: dict :param device_info: historical difference, but same as connection_props :type device_info: dict """ client_id = connection_properties.get('client_id', None) if client_id is None: raise exception.BrickException(_LE( 'Invalid StorPool connection data, no client ID specified.')) volume_id = connection_properties.get('volume', None) if volume_id is None: raise exception.BrickException(_LE( 'Invalid StorPool connection data, no volume ID specified.')) volume = self._attach.volumeName(volume_id) req_id = 'brick-%s-%s' % (client_id, volume_id) self._attach.sync(req_id, volume) self._attach.remove(req_id)
def __init__(self, message=None, **kwargs): self.kwargs = kwargs if 'code' not in self.kwargs: try: self.kwargs['code'] = self.code except AttributeError: pass if not message: try: message = self.message % kwargs except Exception: # kwargs doesn't match a variable in the message # log the issue and the kwargs LOG.exception( _LE("Exception in string format operation. " "msg='%s'"), self.message) for name, value in kwargs.items(): LOG.error(_LE("%(name)s: %(value)s"), { 'name': name, 'value': value }) # at least get the core message out if something happened message = self.message # Put the message in 'msg' so that we can access it. If we have it in # message it will be overshadowed by the class' message attribute self.msg = message super(BrickException, self).__init__(message)
def disconnect_volume(self, connection_properties, device_info): """Disconnect a volume from the local host. The connection_properties are the same as from connect_volume. The device_info is returned from connect_volume. :param connection_properties: The dictionary that describes all of the target volume attributes; it needs to contain the StorPool 'client_id' and the common 'volume' values. :type connection_properties: dict :param device_info: historical difference, but same as connection_props :type device_info: dict """ client_id = connection_properties.get('client_id', None) if client_id is None: raise exception.BrickException( _LE('Invalid StorPool connection data, no client ID specified.' )) volume_id = connection_properties.get('volume', None) if volume_id is None: raise exception.BrickException( _LE('Invalid StorPool connection data, no volume ID specified.' )) volume = self._attach.volumeName(volume_id) req_id = 'brick-%s-%s' % (client_id, volume_id) self._attach.sync(req_id, volume) self._attach.remove(req_id)
def create_lv_snapshot(self, name, source_lv_name, lv_type='default'): """Creates a snapshot of a logical volume. :param name: Name to assign to new snapshot :param source_lv_name: Name of Logical Volume to snapshot :param lv_type: Type of LV (default or thin) """ source_lvref = self.get_volume(source_lv_name) if source_lvref is None: LOG.error(_LE("Trying to create snapshot by non-existent LV: %s"), source_lv_name) raise exception.VolumeDeviceNotFound(device=source_lv_name) cmd = LVM.LVM_CMD_PREFIX + ['lvcreate', '--name', name, '--snapshot', '%s/%s' % (self.vg_name, source_lv_name)] if lv_type != 'thin': size = source_lvref['size'] cmd.extend(['-L', '%sg' % (size)]) try: self._execute(*cmd, root_helper=self._root_helper, run_as_root=True) except putils.ProcessExecutionError as err: LOG.exception(_LE('Error creating snapshot')) LOG.error(_LE('Cmd :%s'), err.cmd) LOG.error(_LE('StdOut :%s'), err.stdout) LOG.error(_LE('StdErr :%s'), err.stderr) raise
def create_lv_snapshot(self, name, source_lv_name, lv_type='default'): """Creates a snapshot of a logical volume. :param name: Name to assign to new snapshot :param source_lv_name: Name of Logical Volume to snapshot :param lv_type: Type of LV (default or thin) """ source_lvref = self.get_volume(source_lv_name) if source_lvref is None: LOG.error(_LE("Trying to create snapshot by non-existent LV: %s"), source_lv_name) raise exception.VolumeDeviceNotFound(device=source_lv_name) cmd = LVM.LVM_CMD_PREFIX + [ 'lvcreate', '--name', name, '--snapshot', '%s/%s' % (self.vg_name, source_lv_name) ] if lv_type != 'thin': size = source_lvref['size'] cmd.extend(['-L', '%sg' % (size)]) try: self._execute(*cmd, root_helper=self._root_helper, run_as_root=True) except putils.ProcessExecutionError as err: LOG.exception(_LE('Error creating snapshot')) LOG.error(_LE('Cmd :%s'), err.cmd) LOG.error(_LE('StdOut :%s'), err.stdout) LOG.error(_LE('StdErr :%s'), err.stderr) raise
def get_volume_paths(self, connection_properties): """Return the list of existing paths for a volume. The job of this method is to find out what paths in the system are associated with a volume as described by the connection_properties. :param connection_properties: The dictionary that describes all of the target volume attributes; it needs to contain 'volume' and 'device_path' values. :type connection_properties: dict """ volume_id = connection_properties.get('volume', None) if volume_id is None: raise exception.BrickException( _LE('Invalid StorPool connection data, no volume ID specified.' )) volume = self._attach.volumeName(volume_id) path = '/dev/storpool/' + volume dpath = connection_properties.get('device_path', None) if dpath is not None and dpath != path: raise exception.BrickException( _LE('Internal error: StorPool volume path {path} does not ' 'match device path {dpath}'), { path: path, dpath: dpath }) return [path]
def rename_volume(self, lv_name, new_name): """Change the name of an existing volume.""" try: self._execute('lvrename', self.vg_name, lv_name, new_name, root_helper=self._root_helper, run_as_root=True) except putils.ProcessExecutionError as err: LOG.exception(_LE('Error renaming logical volume')) LOG.error(_LE('Cmd :%s'), err.cmd) LOG.error(_LE('StdOut :%s'), err.stdout) LOG.error(_LE('StdErr :%s'), err.stderr) raise
def deactivate_lv(self, name): lv_path = self.vg_name + '/' + self._mangle_lv_name(name) cmd = ['lvchange', '-a', 'n'] cmd.append(lv_path) try: self._execute(*cmd, root_helper=self._root_helper, run_as_root=True) except putils.ProcessExecutionError as err: LOG.exception(_LE('Error deactivating LV')) LOG.error(_LE('Cmd :%s'), err.cmd) LOG.error(_LE('StdOut :%s'), err.stdout) LOG.error(_LE('StdErr :%s'), err.stderr) raise
def _connect_tcp_socket(self, client_ip, client_port): """Connect to TCP socket.""" sock = None for res in socket.getaddrinfo(client_ip, client_port, socket.AF_UNSPEC, socket.SOCK_STREAM): aff, socktype, proto, canonname, saa = res try: sock = socket.socket(aff, socktype, proto) except socket.error: sock = None continue try: sock.connect(saa) except socket.error: sock.close() sock = None continue break if sock is None: LOG.error(_LE("Cannot connect TCP socket")) return sock
def _get_hba_channel_scsi_target(self, hba): """Try to get the HBA channel and SCSI target for an HBA. This method only works for Fibre Channel targets that implement a single WWNN for all ports, so caller should expect us to return either None or an empty list. :returns: List or None """ # Leave only the number from the host_device field (ie: host6) host_device = hba['host_device'] if host_device and len(host_device) > 4: host_device = host_device[4:] path = '/sys/class/fc_transport/target%s:' % host_device cmd = 'grep %(wwnn)s %(path)s*/node_name' % { 'wwnn': hba['node_name'], 'path': path } try: out, _err = self._execute(cmd) return [ line.split('/')[4].split(':')[1:] for line in out.split('\n') if line.startswith(path) ] except Exception as exc: LOG.error( _LE('Could not get HBA channel and SCSI target ID, ' 'path: %(path)s, reason: %(reason)s'), { 'path': path, 'reason': exc }) return None
def __init__(self, root_helper, driver=None, *args, **kwargs): super(StorPoolConnector, self).__init__(root_helper, driver=driver, *args, **kwargs) if storpool is None: raise exception.BrickException(_LE( 'Could not import the StorPool API bindings')) try: self._attach = spopenstack.AttachDB(log=LOG) self._attach.api() except Exception as e: raise exception.BrickException(_LE( 'Could not initialize the StorPool API bindings: %s') % (e))
def get_volume_encryptor(root_helper, connection_info, keymgr, execute=None, *args, **kwargs): """Creates a VolumeEncryptor used to encrypt the specified volume. :param: the connection information used to attach the volume :returns VolumeEncryptor: the VolumeEncryptor for the volume """ encryptor = nop.NoOpEncryptor(root_helper=root_helper, connection_info=connection_info, keymgr=keymgr, execute=execute, *args, **kwargs) location = kwargs.get('control_location', None) if location and location.lower() == 'front-end': # case insensitive provider = kwargs.get('provider') # TODO(lyarwood): Remove the following in Pike and raise an # ERROR if provider is not a key in SUPPORTED_ENCRYPTION_PROVIDERS. # Until then continue to allow both the class name and path to be used. if provider in LEGACY_PROVIDER_CLASS_TO_FORMAT_MAP: LOG.warning(_LW("Use of the in tree encryptor class %(provider)s" " by directly referencing the implementation class" " will be blocked in the Pike release of" " os-brick."), {'provider': provider}) provider = LEGACY_PROVIDER_CLASS_TO_FORMAT_MAP[provider] if provider in FORMAT_TO_FRONTEND_ENCRYPTOR_MAP: provider = FORMAT_TO_FRONTEND_ENCRYPTOR_MAP[provider] elif provider is None: provider = "os_brick.encryptors.nop.NoOpEncryptor" else: LOG.warning(_LW("Use of the out of tree encryptor class " "%(provider)s will be blocked with the Pike " "release of os-brick."), {'provider': provider}) try: encryptor = importutils.import_object( provider, root_helper, connection_info, keymgr, execute, **kwargs) except Exception as e: LOG.error(_LE("Error instantiating %(provider)s: %(exception)s"), {'provider': provider, 'exception': e}) raise msg = ("Using volume encryptor '%(encryptor)s' for connection: " "%(connection_info)s" % {'encryptor': encryptor, 'connection_info': connection_info}) LOG.debug(strutils.mask_password(msg)) return encryptor
def deactivate_lv(self, name): lv_path = self.vg_name + '/' + self._mangle_lv_name(name) cmd = ['lvchange', '-a', 'n'] cmd.append(lv_path) try: self._execute(*cmd, root_helper=self._root_helper, run_as_root=True) except putils.ProcessExecutionError as err: LOG.exception(_LE('Error deactivating LV')) LOG.error(_LE('Cmd :%s'), err.cmd) LOG.error(_LE('StdOut :%s'), err.stdout) LOG.error(_LE('StdErr :%s'), err.stderr) raise # Wait until lv is deactivated to return in # order to prevent a race condition. self._wait_for_volume_deactivation(name)
def __init__(self, root_helper, driver=None, *args, **kwargs): super(StorPoolConnector, self).__init__(root_helper, driver=driver, *args, **kwargs) if storpool is None: raise exception.BrickException( _LE('Could not import the StorPool API bindings')) try: self._attach = spopenstack.AttachDB(log=LOG) self._attach.api() except Exception as e: raise exception.BrickException( _LE('Could not initialize the StorPool API bindings: %s') % (e))
def check_valid_device(self, path, *args, **kwargs): try: with open(path, 'r') as dev: dev.read(1) except IOError: LOG.exception( _LE("Failed to access the device on the path " "%(path)s"), {"path": path}) return False return True
def _log_cli_err(self, err): """Dumps the full command output to a logfile in error cases.""" LOG.error( _LE("CLI fail: '%(cmd)s' = %(code)s\nout: %(stdout)s\n" "err: %(stderr)s"), { 'cmd': err.cmd, 'code': err.exit_code, 'stdout': err.stdout, 'stderr': err.stderr })
def extend_volume(self, lv_name, new_size): """Extend the size of an existing volume.""" # Volumes with snaps have attributes 'o' or 'O' and will be # deactivated, but Thin Volumes with snaps have attribute 'V' # and won't be deactivated because the lv_has_snapshot method looks # for 'o' or 'O' if self.lv_has_snapshot(lv_name): self.deactivate_lv(lv_name) try: cmd = LVM.LVM_CMD_PREFIX + ['lvextend', '-L', new_size, '%s/%s' % (self.vg_name, lv_name)] self._execute(*cmd, root_helper=self._root_helper, run_as_root=True) except putils.ProcessExecutionError as err: LOG.exception(_LE('Error extending Volume')) LOG.error(_LE('Cmd :%s'), err.cmd) LOG.error(_LE('StdOut :%s'), err.stdout) LOG.error(_LE('StdErr :%s'), err.stderr) raise
def connect_volume(self, connection_properties): volume_connected = False for (initiator_name, target_portal, target_iqn, target_lun) in self._get_all_paths(connection_properties): try: msg = _LI("Attempting to establish an iSCSI session to " "target %(target_iqn)s on portal %(target_portal)s " "accessing LUN %(target_lun)s using initiator " "%(initiator_name)s.") LOG.info( msg, dict(target_portal=target_portal, target_iqn=target_iqn, target_lun=target_lun, initiator_name=initiator_name)) self._iscsi_utils.login_storage_target( target_lun=target_lun, target_iqn=target_iqn, target_portal=target_portal, auth_username=connection_properties.get('auth_username'), auth_password=connection_properties.get('auth_password'), mpio_enabled=self.use_multipath, initiator_name=initiator_name, ensure_lun_available=False) self._iscsi_utils.ensure_lun_available( target_iqn=target_iqn, target_lun=target_lun, rescan_attempts=self.device_scan_attempts, retry_interval=self.device_scan_interval) if not volume_connected: (device_number, device_path) = ( self._iscsi_utils.get_device_number_and_path( target_iqn, target_lun)) volume_connected = True if not self.use_multipath: break except os_win_exc.OSWinException: LOG.exception(_LE("Could not establish the iSCSI session.")) if not volume_connected: raise exception.BrickException( _("Could not connect volume %s.") % connection_properties) scsi_wwn = self._get_scsi_wwn(device_number) device_info = { 'type': 'block', 'path': device_path, 'number': device_number, 'scsi_wwn': scsi_wwn } return device_info
def activate_lv(self, name, is_snapshot=False, permanent=False): """Ensure that logical volume/snapshot logical volume is activated. :param name: Name of LV to activate :param is_snapshot: whether LV is a snapshot :param permanent: whether we should drop skipactivation flag :raises: putils.ProcessExecutionError """ # This is a no-op if requested for a snapshot on a version # of LVM that doesn't support snapshot activation. # (Assume snapshot LV is always active.) if is_snapshot and not self.supports_snapshot_lv_activation: return lv_path = self.vg_name + '/' + self._mangle_lv_name(name) # Must pass --yes to activate both the snap LV and its origin LV. # Otherwise lvchange asks if you would like to do this interactively, # and fails. cmd = ['lvchange', '-a', 'y', '--yes'] if self.supports_lvchange_ignoreskipactivation: cmd.append('-K') # If permanent=True is specified, drop the skipactivation flag in # order to make this LV automatically activated after next reboot. if permanent: cmd += ['-k', 'n'] cmd.append(lv_path) try: self._execute(*cmd, root_helper=self._root_helper, run_as_root=True) except putils.ProcessExecutionError as err: LOG.exception(_LE('Error activating LV')) LOG.error(_LE('Cmd :%s'), err.cmd) LOG.error(_LE('StdOut :%s'), err.stdout) LOG.error(_LE('StdErr :%s'), err.stderr) raise
def create_volume(self, name, size_str, lv_type='default', mirror_count=0): """Creates a logical volume on the object's VG. :param name: Name to use when creating Logical Volume :param size_str: Size to use when creating Logical Volume :param lv_type: Type of Volume (default or thin) :param mirror_count: Use LVM mirroring with specified count """ if lv_type == 'thin': pool_path = '%s/%s' % (self.vg_name, self.vg_thin_pool) cmd = LVM.LVM_CMD_PREFIX + [ 'lvcreate', '-T', '-V', size_str, '-n', name, pool_path ] else: cmd = LVM.LVM_CMD_PREFIX + [ 'lvcreate', '-n', name, self.vg_name, '-L', size_str ] if mirror_count > 0: cmd.extend( ['-m', mirror_count, '--nosync', '--mirrorlog', 'mirrored']) terras = int(size_str[:-1]) / 1024.0 if terras >= 1.5: rsize = int(2**math.ceil(math.log(terras) / math.log(2))) # NOTE(vish): Next power of two for region size. See: # http://red.ht/U2BPOD cmd.extend(['-R', str(rsize)]) try: self._execute(*cmd, root_helper=self._root_helper, run_as_root=True) except putils.ProcessExecutionError as err: LOG.exception(_LE('Error creating Volume')) LOG.error(_LE('Cmd :%s'), err.cmd) LOG.error(_LE('StdOut :%s'), err.stdout) LOG.error(_LE('StdErr :%s'), err.stderr) raise
def extend_volume(self, lv_name, new_size): """Extend the size of an existing volume.""" # Volumes with snaps have attributes 'o' or 'O' and will be # deactivated, but Thin Volumes with snaps have attribute 'V' # and won't be deactivated because the lv_has_snapshot method looks # for 'o' or 'O' if self.lv_has_snapshot(lv_name): self.deactivate_lv(lv_name) try: cmd = LVM.LVM_CMD_PREFIX + [ 'lvextend', '-L', new_size, '%s/%s' % (self.vg_name, lv_name) ] self._execute(*cmd, root_helper=self._root_helper, run_as_root=True) except putils.ProcessExecutionError as err: LOG.exception(_LE('Error extending Volume')) LOG.error(_LE('Cmd :%s'), err.cmd) LOG.error(_LE('StdOut :%s'), err.stdout) LOG.error(_LE('StdErr :%s'), err.stderr) raise
def check_multipath_support(enforce_multipath): hostutils = utilsfactory.get_hostutils() mpio_enabled = hostutils.check_server_feature(hostutils.FEATURE_MPIO) if not mpio_enabled: err_msg = _LE( "Using multipath connections for iSCSI and FC disks " "requires the Multipath IO Windows feature to be " "enabled. MPIO must be configured to claim such devices.") LOG.error(err_msg) if enforce_multipath: raise exception.BrickException(err_msg) return False return True
def is_multipath_running(enforce_multipath, root_helper): try: priv_rootwrap.execute('multipathd', 'show', 'status', run_as_root=True, root_helper=root_helper) except putils.ProcessExecutionError as err: LOG.error(_LE('multipathd is not running: exit code %(err)s'), {'err': err.exit_code}) if enforce_multipath: raise return False return True
def create_volume(self, name, size_str, lv_type='default', mirror_count=0): """Creates a logical volume on the object's VG. :param name: Name to use when creating Logical Volume :param size_str: Size to use when creating Logical Volume :param lv_type: Type of Volume (default or thin) :param mirror_count: Use LVM mirroring with specified count """ if lv_type == 'thin': pool_path = '%s/%s' % (self.vg_name, self.vg_thin_pool) cmd = LVM.LVM_CMD_PREFIX + ['lvcreate', '-T', '-V', size_str, '-n', name, pool_path] else: cmd = LVM.LVM_CMD_PREFIX + ['lvcreate', '-n', name, self.vg_name, '-L', size_str] if mirror_count > 0: cmd.extend(['-m', mirror_count, '--nosync', '--mirrorlog', 'mirrored']) terras = int(size_str[:-1]) / 1024.0 if terras >= 1.5: rsize = int(2 ** math.ceil(math.log(terras) / math.log(2))) # NOTE(vish): Next power of two for region size. See: # http://red.ht/U2BPOD cmd.extend(['-R', str(rsize)]) try: self._execute(*cmd, root_helper=self._root_helper, run_as_root=True) except putils.ProcessExecutionError as err: LOG.exception(_LE('Error creating Volume')) LOG.error(_LE('Cmd :%s'), err.cmd) LOG.error(_LE('StdOut :%s'), err.stdout) LOG.error(_LE('StdErr :%s'), err.stderr) raise
def _discover_iscsi_portals(self, connection_properties): if all([ key in connection_properties for key in ('target_portals', 'target_iqns') ]): # Use targets specified by connection_properties return zip(connection_properties['target_portals'], connection_properties['target_iqns']) out = None iscsi_transport = ('iser' if self._get_transport() == 'iser' else 'default') if connection_properties.get('discovery_auth_method'): try: self._run_iscsiadm_update_discoverydb(connection_properties, iscsi_transport) except putils.ProcessExecutionError as exception: # iscsiadm returns 6 for "db record not found" if exception.exit_code == 6: # Create a new record for this target and update the db self._run_iscsiadm_bare([ '-m', 'discoverydb', '-t', 'sendtargets', '-p', connection_properties['target_portal'], '-I', iscsi_transport, '--op', 'new' ], check_exit_code=[0, 255]) self._run_iscsiadm_update_discoverydb( connection_properties) else: LOG.error( _LE("Unable to find target portal: " "%(target_portal)s."), { 'target_portal': connection_properties['target_portal'] }) raise out = self._run_iscsiadm_bare([ '-m', 'discoverydb', '-t', 'sendtargets', '-I', iscsi_transport, '-p', connection_properties['target_portal'], '--discover' ], check_exit_code=[0, 255])[0] or "" else: out = self._run_iscsiadm_bare([ '-m', 'discovery', '-t', 'sendtargets', '-I', iscsi_transport, '-p', connection_properties['target_portal'] ], check_exit_code=[0, 255])[0] or "" return self._get_target_portals_from_iscsiadm_output(out)
def _discover_iscsi_portals(self, connection_properties): if all([key in connection_properties for key in ('target_portals', 'target_iqns')]): # Use targets specified by connection_properties return zip(connection_properties['target_portals'], connection_properties['target_iqns']) out = None iscsi_transport = ('iser' if self._get_transport() == 'iser' else 'default') if connection_properties.get('discovery_auth_method'): try: self._run_iscsiadm_update_discoverydb(connection_properties, iscsi_transport) except putils.ProcessExecutionError as exception: # iscsiadm returns 6 for "db record not found" if exception.exit_code == 6: # Create a new record for this target and update the db self._run_iscsiadm_bare( ['-m', 'discoverydb', '-t', 'sendtargets', '-p', connection_properties['target_portal'], '-I', iscsi_transport, '--op', 'new'], check_exit_code=[0, 255]) self._run_iscsiadm_update_discoverydb( connection_properties ) else: LOG.error(_LE("Unable to find target portal: " "%(target_portal)s."), {'target_portal': connection_properties[ 'target_portal']}) raise out = self._run_iscsiadm_bare( ['-m', 'discoverydb', '-t', 'sendtargets', '-I', iscsi_transport, '-p', connection_properties['target_portal'], '--discover'], check_exit_code=[0, 255])[0] or "" else: out = self._run_iscsiadm_bare( ['-m', 'discovery', '-t', 'sendtargets', '-I', iscsi_transport, '-p', connection_properties['target_portal']], check_exit_code=[0, 255])[0] or "" return self._get_target_portals_from_iscsiadm_output(out)
def __init__(self, root_helper, driver=None, *args, **kwargs): self.cli_path = os.getenv('HUAWEISDSHYPERVISORCLI_PATH') if not self.cli_path: self.cli_path = '/usr/local/bin/sds/sds_cli' LOG.debug("CLI path is not configured, using default %s.", self.cli_path) if not os.path.isfile(self.cli_path): self.iscliexist = False LOG.error(_LE('SDS CLI file not found, ' 'HuaweiStorHyperConnector init failed.')) super(HuaweiStorHyperConnector, self).__init__(root_helper, driver=driver, *args, **kwargs)
def connect_volume(self, connection_properties): """Connect to a volume. :param connection_properties: The dictionary that describes all of the target volume attributes; it needs to contain the StorPool 'client_id' and the common 'volume' and 'access_mode' values. :type connection_properties: dict :returns: dict """ client_id = connection_properties.get('client_id', None) if client_id is None: raise exception.BrickException( _LE('Invalid StorPool connection data, no client ID specified.' )) volume_id = connection_properties.get('volume', None) if volume_id is None: raise exception.BrickException( _LE('Invalid StorPool connection data, no volume ID specified.' )) volume = self._attach.volumeName(volume_id) mode = connection_properties.get('access_mode', None) if mode is None or mode not in ('rw', 'ro'): raise exception.BrickException( _LE('Invalid access_mode specified in the connection data.')) req_id = 'brick-%s-%s' % (client_id, volume_id) self._attach.add( req_id, { 'volume': volume, 'type': 'brick', 'id': req_id, 'rights': 1 if mode == 'ro' else 2, 'volsnap': False }) self._attach.sync(req_id, None) return {'type': 'block', 'path': '/dev/storpool/' + volume}
def _get_thin_pool_free_space(self, vg_name, thin_pool_name): """Returns available thin pool free space. :param vg_name: the vg where the pool is placed :param thin_pool_name: the thin pool to gather info for :returns: Free space in GB (float), calculated using data_percent """ cmd = LVM.LVM_CMD_PREFIX + [ 'lvs', '--noheadings', '--unit=g', '-o', 'size,data_percent', '--separator', ':', '--nosuffix' ] # NOTE(gfidente): data_percent only applies to some types of LV so we # make sure to append the actual thin pool name cmd.append("/dev/%s/%s" % (vg_name, thin_pool_name)) free_space = 0.0 try: (out, err) = self._execute(*cmd, root_helper=self._root_helper, run_as_root=True) if out is not None: out = out.strip() data = out.split(':') pool_size = float(data[0]) data_percent = float(data[1]) consumed_space = pool_size / 100 * data_percent free_space = pool_size - consumed_space free_space = round(free_space, 2) except putils.ProcessExecutionError as err: LOG.exception(_LE('Error querying thin pool about data_percent')) LOG.error(_LE('Cmd :%s'), err.cmd) LOG.error(_LE('StdOut :%s'), err.stdout) LOG.error(_LE('StdErr :%s'), err.stderr) return free_space
def __init__(self, client, name, snapshot=None, read_only=False): if snapshot is not None: snapshot = encodeutils.safe_encode(snapshot) try: self.image = client.rbd.Image(client.ioctx, encodeutils.safe_encode(name), snapshot=snapshot, read_only=read_only) except client.rbd.Error: LOG.exception(_LE("error opening rbd image %s"), name) client.disconnect() raise self.client = client
def _get_thin_pool_free_space(self, vg_name, thin_pool_name): """Returns available thin pool free space. :param vg_name: the vg where the pool is placed :param thin_pool_name: the thin pool to gather info for :returns: Free space in GB (float), calculated using data_percent """ cmd = LVM.LVM_CMD_PREFIX + ['lvs', '--noheadings', '--unit=g', '-o', 'size,data_percent', '--separator', ':', '--nosuffix'] # NOTE(gfidente): data_percent only applies to some types of LV so we # make sure to append the actual thin pool name cmd.append("/dev/%s/%s" % (vg_name, thin_pool_name)) free_space = 0.0 try: (out, err) = self._execute(*cmd, root_helper=self._root_helper, run_as_root=True) if out is not None: out = out.strip() data = out.split(':') pool_size = float(data[0]) data_percent = float(data[1]) consumed_space = pool_size / 100 * data_percent free_space = pool_size - consumed_space free_space = round(free_space, 2) except putils.ProcessExecutionError as err: LOG.exception(_LE('Error querying thin pool about data_percent')) LOG.error(_LE('Cmd :%s'), err.cmd) LOG.error(_LE('StdOut :%s'), err.stdout) LOG.error(_LE('StdErr :%s'), err.stderr) return free_space
def __init__(self, client, name, snapshot=None, read_only=False): if snapshot is not None: snapshot = utils.convert_str(snapshot) try: self.image = client.rbd.Image(client.ioctx, utils.convert_str(name), snapshot=snapshot, read_only=read_only) except client.rbd.Error: LOG.exception(_LE("error opening rbd image %s"), name) client.disconnect() raise self.client = client
def __init__(self, root_helper, driver=None, *args, **kwargs): self.cli_path = os.getenv('HUAWEISDSHYPERVISORCLI_PATH') if not self.cli_path: self.cli_path = '/usr/local/bin/sds/sds_cli' LOG.debug("CLI path is not configured, using default %s.", self.cli_path) if not os.path.isfile(self.cli_path): self.iscliexist = False LOG.error( _LE('SDS CLI file not found, ' 'HuaweiStorHyperConnector init failed.')) super(HuaweiStorHyperConnector, self).__init__(root_helper, driver=driver, *args, **kwargs)
def get_volume_encryptor(root_helper, connection_info, keymgr, execute=None, *args, **kwargs): """Creates a VolumeEncryptor used to encrypt the specified volume. :param: the connection information used to attach the volume :returns VolumeEncryptor: the VolumeEncryptor for the volume """ encryptor = nop.NoOpEncryptor(root_helper=root_helper, connection_info=connection_info, keymgr=keymgr, execute=execute, *args, **kwargs) location = kwargs.get('control_location', None) if location and location.lower() == 'front-end': # case insensitive provider = kwargs.get('provider') if provider == 'LuksEncryptor' or 'LuksEncryptor' in provider: provider = 'os_brick.encryptors.luks.LuksEncryptor' elif (provider == 'CryptsetupEncryptor' or 'CryptsetupEncryptor' in provider): provider = \ 'os_brick.encryptors.cryptsetup.CryptsetupEncryptor' elif (provider == 'NoOpEncryptor' or 'NoOpEncryptor' in provider): provider = 'os_brick.encryptors.nop.NoOpEncryptor' try: encryptor = importutils.import_object( provider, root_helper, connection_info, keymgr, execute, **kwargs) except Exception as e: LOG.error(_LE("Error instantiating %(provider)s: %(exception)s"), {'provider': provider, 'exception': e}) raise msg = ("Using volume encryptor '%(encryptor)s' for connection: " "%(connection_info)s" % {'encryptor': encryptor, 'connection_info': connection_info}) LOG.debug(strutils.mask_password(msg)) return encryptor
def connect_volume(self, connection_properties): volume_connected = False for (initiator_name, target_portal, target_iqn, target_lun) in self._get_all_paths(connection_properties): try: msg = _LI("Attempting to establish an iSCSI session to " "target %(target_iqn)s on portal %(target_portal)s " "accessing LUN %(target_lun)s using initiator " "%(initiator_name)s.") LOG.info(msg, dict(target_portal=target_portal, target_iqn=target_iqn, target_lun=target_lun, initiator_name=initiator_name)) self._iscsi_utils.login_storage_target( target_lun=target_lun, target_iqn=target_iqn, target_portal=target_portal, auth_username=connection_properties.get('auth_username'), auth_password=connection_properties.get('auth_password'), mpio_enabled=self.use_multipath, initiator_name=initiator_name, rescan_attempts=self.device_scan_attempts) if not volume_connected: (device_number, device_path) = ( self._iscsi_utils.get_device_number_and_path( target_iqn, target_lun)) volume_connected = True if not self.use_multipath: break except os_win_exc.OSWinException: LOG.exception(_LE("Could not establish the iSCSI session.")) if not volume_connected: raise exception.BrickException( _("Could not connect volume %s.") % connection_properties) scsi_wwn = self._get_scsi_wwn(device_number) device_info = {'type': 'block', 'path': device_path, 'number': device_number, 'scsi_wwn': scsi_wwn} return device_info
def check_valid_device(self, path, run_as_root=True): cmd = ('dd', 'if=%(path)s' % {"path": path}, 'of=/dev/null', 'count=1') out, info = None, None try: out, info = self._execute(*cmd, run_as_root=run_as_root, root_helper=self._root_helper) except putils.ProcessExecutionError as e: LOG.error(_LE("Failed to access the device on the path " "%(path)s: %(error)s."), {"path": path, "error": e.stderr}) return False # If the info is none, the path does not exist. if info is None: return False return True
def _cli_cmd(self, method, volume_name): LOG.debug("Enter into _cli_cmd.") if not self.iscliexist: msg = _("SDS command line doesn't exist, " "can't execute SDS command.") raise exception.BrickException(message=msg) if not method or volume_name is None: return cmd = [self.cli_path, '-c', method, '-v', volume_name] out, clilog = self._execute(*cmd, run_as_root=False, root_helper=self._root_helper) analyse_result = self._analyze_output(out) LOG.debug('%(method)s volume returns %(analyse_result)s.', {'method': method, 'analyse_result': analyse_result}) if clilog: LOG.error(_LE("SDS CLI output some log: %s."), clilog) return analyse_result
def disconnect_volume(self, connection_properties, device_info): """Detach the volume from instance_name. :param connection_properties: The dictionary that describes all of the target volume attributes. :type connection_properties: dict :param device_info: historical difference, but same as connection_props :type device_info: dict connection_properties for iSCSI must include: target_portal(s) - IP and optional port target_iqn(s) - iSCSI Qualified Name target_lun(s) - LUN id of the volume """ if self.use_multipath: self._rescan_multipath() host_device = multipath_device = None host_devices = self._get_device_path(connection_properties) # Choose an accessible host device for dev in host_devices: if os.path.exists(dev): host_device = dev device_wwn = self._linuxscsi.get_scsi_wwn(dev) (multipath_device, multipath_id) = (super( ISCSIConnector, self)._discover_mpath_device(device_wwn, connection_properties, dev)) if multipath_device: break if not host_device: LOG.error(_LE("No accessible volume device: %(host_devices)s"), {'host_devices': host_devices}) raise exception.VolumeDeviceNotFound(device=host_devices) if multipath_device: device_realpath = os.path.realpath(host_device) self._linuxscsi.remove_multipath_device(device_realpath) return self._disconnect_volume_multipath_iscsi( connection_properties, multipath_device) # When multiple portals/iqns/luns are specified, we need to remove # unused devices created by logging into other LUNs' session. for props in self._iterate_all_targets(connection_properties): self._disconnect_volume_iscsi(props)
def disconnect_volume(self, connection_properties, device_info): """Detach the volume from instance_name. :param connection_properties: The dictionary that describes all of the target volume attributes. :type connection_properties: dict :param device_info: historical difference, but same as connection_props :type device_info: dict connection_properties for iSCSI must include: target_portal(s) - IP and optional port target_iqn(s) - iSCSI Qualified Name target_lun(s) - LUN id of the volume """ if self.use_multipath: self._rescan_multipath() host_device = multipath_device = None host_devices = self._get_device_path(connection_properties) # Choose an accessible host device for dev in host_devices: if os.path.exists(dev): host_device = dev device_wwn = self._linuxscsi.get_scsi_wwn(dev) (multipath_device, multipath_id) = (super( ISCSIConnector, self)._discover_mpath_device( device_wwn, connection_properties, dev)) if multipath_device: break if not host_device: LOG.error(_LE("No accessible volume device: %(host_devices)s"), {'host_devices': host_devices}) raise exception.VolumeDeviceNotFound(device=host_devices) if multipath_device: device_realpath = os.path.realpath(host_device) self._linuxscsi.remove_multipath_device(device_realpath) return self._disconnect_volume_multipath_iscsi( connection_properties, multipath_device) # When multiple portals/iqns/luns are specified, we need to remove # unused devices created by logging into other LUNs' session. for props in self._iterate_all_targets(connection_properties): self._disconnect_volume_iscsi(props)
def check_valid_device(self, path, run_as_root=True): """Verify an existing RBD handle is connected and valid.""" rbd_handle = path if rbd_handle is None: return False original_offset = rbd_handle.tell() try: rbd_handle.read(4096) except Exception as e: LOG.error(_LE("Failed to access RBD device handle: %(error)s"), {"error": e}) return False finally: rbd_handle.seek(original_offset, 0) return True
def check_valid_device(self, path, run_as_root=True): cmd = ('dd', 'if=%(path)s' % {"path": path}, 'of=/dev/null', 'count=1') out, info = None, None try: out, info = self._execute(*cmd, run_as_root=run_as_root, root_helper=self._root_helper) except putils.ProcessExecutionError as e: LOG.error( _LE("Failed to access the device on the path " "%(path)s: %(error)s."), { "path": path, "error": e.stderr }) return False # If the info is none, the path does not exist. if info is None: return False return True
def create_thin_pool(self, name=None): """Creates a thin provisioning pool for this VG. The syntax here is slightly different than the default lvcreate -T, so we'll just write a custom cmd here and do it. :param name: Name to use for pool, default is "<vg-name>-pool" :returns: The size string passed to the lvcreate command """ if not LVM.supports_thin_provisioning(self._root_helper): LOG.error( _LE('Requested to setup thin provisioning, ' 'however current LVM version does not ' 'support it.')) return None if name is None: name = '%s-pool' % self.vg_name vg_pool_name = '%s/%s' % (self.vg_name, name) size_args = self._calculate_thin_pool_size() cmd = LVM.LVM_CMD_PREFIX + ['lvcreate', '-T'] cmd.extend(size_args) cmd.append(vg_pool_name) LOG.debug( "Creating thin pool '%(pool)s' with size %(size)s of " "total %(free)sg", { 'pool': vg_pool_name, 'size': size_args, 'free': self.vg_free_space }) self._execute(*cmd, root_helper=self._root_helper, run_as_root=True) self.vg_thin_pool = name return
def _cli_cmd(self, method, volume_name): LOG.debug("Enter into _cli_cmd.") if not self.iscliexist: msg = _("SDS command line doesn't exist, " "can't execute SDS command.") raise exception.BrickException(message=msg) if not method or volume_name is None: return cmd = [self.cli_path, '-c', method, '-v', volume_name] out, clilog = self._execute(*cmd, run_as_root=False, root_helper=self._root_helper) analyse_result = self._analyze_output(out) LOG.debug('%(method)s volume returns %(analyse_result)s.', { 'method': method, 'analyse_result': analyse_result }) if clilog: LOG.error(_LE("SDS CLI output some log: %s."), clilog) return analyse_result
def create_thin_pool(self, name=None): """Creates a thin provisioning pool for this VG. The syntax here is slightly different than the default lvcreate -T, so we'll just write a custom cmd here and do it. :param name: Name to use for pool, default is "<vg-name>-pool" :returns: The size string passed to the lvcreate command """ if not LVM.supports_thin_provisioning(self._root_helper): LOG.error(_LE('Requested to setup thin provisioning, ' 'however current LVM version does not ' 'support it.')) return None if name is None: name = '%s-pool' % self.vg_name vg_pool_name = '%s/%s' % (self.vg_name, name) size_args = self._calculate_thin_pool_size() cmd = LVM.LVM_CMD_PREFIX + ['lvcreate', '-T'] cmd.extend(size_args) cmd.append(vg_pool_name) LOG.debug("Creating thin pool '%(pool)s' with size %(size)s of " "total %(free)sg", {'pool': vg_pool_name, 'size': size_args, 'free': self.vg_free_space}) self._execute(*cmd, root_helper=self._root_helper, run_as_root=True) self.vg_thin_pool = name return
def _get_hba_channel_scsi_target(self, hba): """Try to get the HBA channel and SCSI target for an HBA. This method only works for Fibre Channel targets that implement a single WWNN for all ports, so caller should expect us to return either None or an empty list. """ # Leave only the number from the host_device field (ie: host6) host_device = hba['host_device'] if host_device and len(host_device) > 4: host_device = host_device[4:] path = '/sys/class/fc_transport/target%s:' % host_device cmd = 'grep %(wwnn)s %(path)s*/node_name' % {'wwnn': hba['node_name'], 'path': path} try: out, _err = self._execute(cmd) return [line.split('/')[4].split(':')[1:] for line in out.split('\n') if line.startswith(path)] except Exception as exc: LOG.error(_LE('Could not get HBA channel and SCSI target ID, ' 'reason: %s'), exc) return None
def _wait_for_device_discovery(host_devices): tries = self.tries for device in host_devices: LOG.debug("Looking for Fibre Channel dev %(device)s", {'device': device}) if os.path.exists(device): self.host_device = device # get the /dev/sdX device. This is used # to find the multipath device. self.device_name = os.path.realpath(device) raise loopingcall.LoopingCallDone() if self.tries >= self.device_scan_attempts: LOG.error(_LE("Fibre Channel volume device not found.")) raise exception.NoFibreChannelVolumeDeviceFound() LOG.info(_LI("Fibre Channel volume device not yet found. " "Will rescan & retry. Try number: %(tries)s."), {'tries': tries}) self._linuxfc.rescan_hosts(hbas, connection_properties['target_lun']) self.tries = self.tries + 1
def extend_volume(self, connection_properties): """Update the attached volume's size. This method will attempt to update the local hosts's volume after the volume has been extended on the remote system. The new volume size in bytes will be returned. If there is a failure to update, then None will be returned. :param connection_properties: The volume connection properties. :returns: new size of the volume. """ # The StorPool client (storpool_block service) running on this host # should have picked up the change already, so it is enough to query # the actual disk device to see if its size is correct. # # TODO(pp): query the API to see if this is really the case volume_id = connection_properties.get('volume', None) if volume_id is None: raise exception.BrickException(_LE( 'Invalid StorPool connection data, no volume ID specified.')) volume = self._attach.volumeName(volume_id) path = '/dev/storpool/' + volume return self._get_device_size(path)
def get_encryption_metadata(context, volume_api, volume_id, connection_info): metadata = {} if ('data' in connection_info and connection_info['data'].get('encrypted', False)): try: metadata = volume_api.get_volume_encryption_metadata(context, volume_id) if not metadata: LOG.warning(_LW( 'Volume %s should be encrypted but there is no ' 'encryption metadata.'), volume_id) except Exception as e: LOG.error(_LE("Failed to retrieve encryption metadata for " "volume %(volume_id)s: %(exception)s"), {'volume_id': volume_id, 'exception': e}) raise if metadata: msg = ("Using volume encryption metadata '%(metadata)s' for " "connection: %(connection_info)s" % {'metadata': metadata, 'connection_info': connection_info}) LOG.debug(strutils.mask_password(msg)) return metadata
def __init__(self, vg_name, root_helper, create_vg=False, physical_volumes=None, lvm_type='default', executor=putils.execute, lvm_conf=None): """Initialize the LVM object. The LVM object is based on an LVM VolumeGroup, one instantiation for each VolumeGroup you have/use. :param vg_name: Name of existing VG or VG to create :param root_helper: Execution root_helper method to use :param create_vg: Indicates the VG doesn't exist and we want to create it :param physical_volumes: List of PVs to build VG on :param lvm_type: VG and Volume type (default, or thin) :param executor: Execute method to use, None uses oslo_concurrency.processutils """ super(LVM, self).__init__(execute=executor, root_helper=root_helper) self.vg_name = vg_name self.pv_list = [] self.vg_size = 0.0 self.vg_free_space = 0.0 self.vg_lv_count = 0 self.vg_uuid = None self.vg_thin_pool = None self.vg_thin_pool_size = 0.0 self.vg_thin_pool_free_space = 0.0 self._supports_snapshot_lv_activation = None self._supports_lvchange_ignoreskipactivation = None self.vg_provisioned_capacity = 0.0 # Ensure LVM_SYSTEM_DIR has been added to LVM.LVM_CMD_PREFIX # before the first LVM command is executed, and use the directory # where the specified lvm_conf file is located as the value. if lvm_conf and os.path.isfile(lvm_conf): lvm_sys_dir = os.path.dirname(lvm_conf) LVM.LVM_CMD_PREFIX = ['env', 'LC_ALL=C', 'LVM_SYSTEM_DIR=' + lvm_sys_dir] if create_vg and physical_volumes is not None: self.pv_list = physical_volumes try: self._create_vg(physical_volumes) except putils.ProcessExecutionError as err: LOG.exception(_LE('Error creating Volume Group')) LOG.error(_LE('Cmd :%s'), err.cmd) LOG.error(_LE('StdOut :%s'), err.stdout) LOG.error(_LE('StdErr :%s'), err.stderr) raise exception.VolumeGroupCreationFailed(vg_name=self.vg_name) if self._vg_exists() is False: LOG.error(_LE('Unable to locate Volume Group %s'), vg_name) raise exception.VolumeGroupNotFound(vg_name=vg_name) # NOTE: we assume that the VG has been activated outside of Cinder if lvm_type == 'thin': pool_name = "%s-pool" % self.vg_name if self.get_volume(pool_name) is None: try: self.create_thin_pool(pool_name) except putils.ProcessExecutionError: # Maybe we just lost the race against another copy of # this driver being in init in parallel - e.g. # cinder-volume and cinder-backup starting in parallel if self.get_volume(pool_name) is None: raise self.vg_thin_pool = pool_name self.activate_lv(self.vg_thin_pool) self.pv_list = self.get_all_physical_volumes(root_helper, vg_name)
def update_volume_group_info(self): """Update VG info for this instantiation. Used to update member fields of object and provide a dict of info for caller. :returns: Dictionaries of VG info """ vg_list = self.get_all_volume_groups(self._root_helper, self.vg_name) if len(vg_list) != 1: LOG.error(_LE('Unable to find VG: %s'), self.vg_name) raise exception.VolumeGroupNotFound(vg_name=self.vg_name) self.vg_size = float(vg_list[0]['size']) self.vg_free_space = float(vg_list[0]['available']) self.vg_lv_count = int(vg_list[0]['lv_count']) self.vg_uuid = vg_list[0]['uuid'] total_vols_size = 0.0 if self.vg_thin_pool is not None: # NOTE(xyang): If providing only self.vg_name, # get_lv_info will output info on the thin pool and all # individual volumes. # get_lv_info(self._root_helper, 'stack-vg') # sudo lvs --noheadings --unit=g -o vg_name,name,size # --nosuffix stack-vg # stack-vg stack-pool 9.51 # stack-vg volume-13380d16-54c3-4979-9d22-172082dbc1a1 1.00 # stack-vg volume-629e13ab-7759-46a5-b155-ee1eb20ca892 1.00 # stack-vg volume-e3e6281c-51ee-464c-b1a7-db6c0854622c 1.00 # # If providing both self.vg_name and self.vg_thin_pool, # get_lv_info will output only info on the thin pool, but not # individual volumes. # get_lv_info(self._root_helper, 'stack-vg', 'stack-pool') # sudo lvs --noheadings --unit=g -o vg_name,name,size # --nosuffix stack-vg/stack-pool # stack-vg stack-pool 9.51 # # We need info on both the thin pool and the volumes, # therefore we should provide only self.vg_name, but not # self.vg_thin_pool here. for lv in self.get_lv_info(self._root_helper, self.vg_name): lvsize = lv['size'] # get_lv_info runs "lvs" command with "--nosuffix". # This removes "g" from "1.00g" and only outputs "1.00". # Running "lvs" command without "--nosuffix" will output # "1.00g" if "g" is the unit. # Remove the unit if it is in lv['size']. if not lv['size'][-1].isdigit(): lvsize = lvsize[:-1] if lv['name'] == self.vg_thin_pool: self.vg_thin_pool_size = lvsize tpfs = self._get_thin_pool_free_space(self.vg_name, self.vg_thin_pool) self.vg_thin_pool_free_space = tpfs else: total_vols_size = total_vols_size + float(lvsize) total_vols_size = round(total_vols_size, 2) self.vg_provisioned_capacity = total_vols_size