def test_silent_verbose_on_failure(self, mock_call, caplog, capsys): mock_call(stdout='stdout\n', stderr='stderr\n', returncode=1) process.call(['ls'], verbose_on_failure=False) out, err = capsys.readouterr() log_lines = '\n'.join([line[-1] for line in caplog.record_tuples]) assert 'Running command: ' in log_lines assert 'ls' in log_lines assert 'stderr' in log_lines assert out == ''
def test_stderr_terminal_and_logfile_off(self, mock_call, caplog, capsys): mock_call(stdout='stdout\n', stderr='some stderr message\n') process.call(['ls'], terminal_verbose=False) out, err = capsys.readouterr() log_lines = [line[-1] for line in caplog.record_tuples] assert 'Running command: ' in log_lines[0] assert 'ls' in log_lines[0] assert 'stderr some stderr message' in log_lines[-1] assert out == ''
def osd_mkfs_bluestore(osd_id, fsid, keyring=None, wal=False, db=False): """ Create the files for the OSD to function. A normal call will look like: ceph-osd --cluster ceph --mkfs --mkkey -i 0 \ --monmap /var/lib/ceph/osd/ceph-0/activate.monmap \ --osd-data /var/lib/ceph/osd/ceph-0 \ --osd-uuid 8d208665-89ae-4733-8888-5d3bfbeeec6c \ --keyring /var/lib/ceph/osd/ceph-0/keyring \ --setuser ceph --setgroup ceph In some cases it is required to use the keyring, when it is passed in as a keywork argument it is used as part of the ceph-osd command """ path = '/var/lib/ceph/osd/%s-%s/' % (conf.cluster, osd_id) monmap = os.path.join(path, 'activate.monmap') system.chown(path) base_command = [ 'ceph-osd', '--cluster', conf.cluster, # undocumented flag, sets the `type` file to contain 'bluestore' '--osd-objectstore', 'bluestore', '--mkfs', '-i', osd_id, '--monmap', monmap, ] supplementary_command = [ '--osd-data', path, '--osd-uuid', fsid, '--setuser', 'ceph', '--setgroup', 'ceph' ] if keyring is not None: base_command.extend(['--keyfile', '-']) if wal: base_command.extend( ['--bluestore-block-wal-path', wal] ) system.chown(wal) if db: base_command.extend( ['--bluestore-block-db-path', db] ) system.chown(db) command = base_command + supplementary_command process.call(command, stdin=keyring, show_command=True)
def set_tag(self, key, value): """ Set the key/value pair as an LVM tag. """ # remove it first if it exists self.clear_tag(key) process.call( [ 'lvchange', '--addtag', '%s=%s' % (key, value), self.lv_path ] ) self.tags[key] = value
def remove_lv(lv): """ Removes a logical volume given it's absolute path. Will return True if the lv is successfully removed or raises a RuntimeError if the removal fails. :param lv: A ``Volume`` object or the path for an LV """ if isinstance(lv, Volume): path = lv.lv_path else: path = lv stdout, stderr, returncode = process.call( [ 'lvremove', '-v', # verbose '-f', # force it path ], show_command=True, terminal_verbose=True, ) if returncode != 0: raise RuntimeError("Unable to remove %s" % path) return True
def osd_id_available(osd_id): """ Checks to see if an osd ID exists and if it's available for reuse. Returns True if it is, False if it isn't. :param osd_id: The osd ID to check """ if osd_id is None: return False bootstrap_keyring = '/var/lib/ceph/bootstrap-osd/%s.keyring' % conf.cluster stdout, stderr, returncode = process.call( [ 'ceph', '--cluster', conf.cluster, '--name', 'client.bootstrap-osd', '--keyring', bootstrap_keyring, 'osd', 'tree', '-f', 'json', ], show_command=True ) if returncode != 0: raise RuntimeError('Unable check if OSD id exists: %s' % osd_id) output = json.loads(''.join(stdout).strip()) osds = output['nodes'] osd = [osd for osd in osds if str(osd['id']) == str(osd_id)] if osd and osd[0].get('status') == "destroyed": return True return False
def luks_format(key, device): """ Decrypt (open) an encrypted device, previously prepared with cryptsetup :param key: dmcrypt secret key, will be used for decrypting :param device: Absolute path to device """ command = [ 'cryptsetup', '--batch-mode', # do not prompt '--key-file', # misnomer, should be key '-', # because we indicate stdin for the key here 'luksFormat', device, ] process.call(command, stdin=key, terminal_verbose=True, show_command=True)
def blkid(device): """ The blkid interface to its CLI, creating an output similar to what is expected from ``lsblk``. In most cases, ``lsblk()`` should be the preferred method for extracting information about a device. There are some corner cases where it might provide information that is otherwise unavailable. The system call uses the ``-p`` flag which bypasses the cache, the caveat being that the keys produced are named completely different to expected names. For example, instead of ``PARTLABEL`` it provides a ``PART_ENTRY_NAME``. A bit of translation between these known keys is done, which is why ``lsblk`` should always be preferred: the output provided here is not as rich, given that a translation of keys is required for a uniform interface with the ``-p`` flag. Label name to expected output chart: cache bypass name expected name UUID UUID TYPE TYPE PART_ENTRY_NAME PARTLABEL PART_ENTRY_UUID PARTUUID """ out, err, rc = process.call( ['blkid', '-p', device] ) return _blkid_parser(' '.join(out))
def _udevadm_info(device): """ Call udevadm and return the output """ cmd = ['udevadm', 'info', '--query=property', device] out, _err, _rc = process.call(cmd) return out
def create_id(fsid, json_secrets, osd_id=None): """ :param fsid: The osd fsid to create, always required :param json_secrets: a json-ready object with whatever secrets are wanted to be passed to the monitor :param osd_id: Reuse an existing ID from an OSD that's been destroyed, if the id does not exist in the cluster a new ID will be created """ bootstrap_keyring = '/var/lib/ceph/bootstrap-osd/%s.keyring' % conf.cluster cmd = [ 'ceph', '--cluster', conf.cluster, '--name', 'client.bootstrap-osd', '--keyring', bootstrap_keyring, '-i', '-', 'osd', 'new', fsid ] if check_id(osd_id): cmd.append(osd_id) stdout, stderr, returncode = process.call( cmd, stdin=json_secrets, show_command=True ) if returncode != 0: raise RuntimeError('Unable to create a new OSD id') return ' '.join(stdout).strip()
def check_id(osd_id): """ Checks to see if an osd ID exists or not. Returns True if it does exist, False if it doesn't. :param osd_id: The osd ID to check """ if osd_id is None: return False bootstrap_keyring = '/var/lib/ceph/bootstrap-osd/%s.keyring' % conf.cluster stdout, stderr, returncode = process.call( [ 'ceph', '--cluster', conf.cluster, '--name', 'client.bootstrap-osd', '--keyring', bootstrap_keyring, 'osd', 'tree', '-f', 'json', ], show_command=True ) if returncode != 0: raise RuntimeError('Unable check if OSD id exists: %s' % osd_id) output = json.loads(''.join(stdout).strip()) osds = output['nodes'] return any([str(osd['id']) == str(osd_id) for osd in osds])
def create_key(): stdout, stderr, returncode = process.call( ['ceph-authtool', '--gen-print-key'], show_command=True) if returncode != 0: raise RuntimeError('Unable to generate a new auth key') return ' '.join(stdout).strip()
def get_dmcrypt_key(osd_id, osd_fsid, lockbox_keyring=None): """ Retrieve the dmcrypt (secret) key stored initially on the monitor. The key is sent initially with JSON, and the Monitor then mangles the name to ``dm-crypt/osd/<fsid>/luks`` The ``lockbox.keyring`` file is required for this operation, and it is assumed it will exist on the path for the same OSD that is being activated. To support scanning, it is optionally configurable to a custom location (e.g. inside a lockbox partition mounted in a temporary location) """ if lockbox_keyring is None: lockbox_keyring = '/var/lib/ceph/osd/%s-%s/lockbox.keyring' % (conf.cluster, osd_id) name = 'client.osd-lockbox.%s' % osd_fsid config_key = 'dm-crypt/osd/%s/luks' % osd_fsid stdout, stderr, returncode = process.call( [ 'ceph', '--cluster', conf.cluster, '--name', name, '--keyring', lockbox_keyring, 'config-key', 'get', config_key ], show_command=True ) if returncode != 0: raise RuntimeError('Unable to retrieve dmcrypt secret') return ' '.join(stdout).strip()
def get_device_from_partuuid(partuuid): """ If a device has a partuuid, query blkid so that it can tell us what that device is """ out, err, rc = process.call( ['blkid', '-t', 'PARTUUID="%s"' % partuuid, '-o', 'device'] ) return ' '.join(out).strip()
def set_tag(self, key, value): """ Set the key/value pair as an LVM tag. Does not "refresh" the values of the current object for its tags. Meant to be a "fire and forget" type of modification. """ # remove it first if it exists if self.tags.get(key): current_value = self.tags[key] tag = "%s=%s" % (key, current_value) process.call(['lvchange', '--deltag', tag, self.lv_api['lv_path']]) process.call( [ 'lvchange', '--addtag', '%s=%s' % (key, value), self.lv_path ] )
def _lsblk_type(device): """ Helper function that will use the ``TYPE`` label output of ``lsblk`` to determine if a device is a partition or disk. It does not process the output to return a boolean, but it does process it to return the """ out, err, rc = process.call( ['sudo', 'blkid', '-s', 'PARTUUID', '-o', 'value', device] ) return ' '.join(out).strip()
def get_partuuid(device): """ If a device is a partition, it will probably have a PARTUUID on it that will persist and can be queried against `blkid` later to detect the actual device """ out, err, rc = process.call( ['blkid', '-s', 'PARTUUID', '-o', 'value', device] ) return ' '.join(out).strip()
def luks_open(key, device, mapping): """ Decrypt (open) an encrypted device, previously prepared with cryptsetup .. note: ceph-disk will require an additional b64decode call for this to work :param key: dmcrypt secret key :param device: absolute path to device :param mapping: mapping name used to correlate device. Usually a UUID """ command = [ 'cryptsetup', '--key-file', '-', 'luksOpen', device, mapping, ] process.call(command, stdin=key, terminal_verbose=True, show_command=True)
def get_part_entry_type(device): """ Parses the ``ID_PART_ENTRY_TYPE`` from the "low level" (bypasses the cache) output that uses the ``udev`` type of output. This output is intended to be used for udev rules, but it is useful in this case as it is the only consistent way to retrieve the GUID used by ceph-disk to identify devices. """ out, err, rc = process.call(['blkid', '-p', '-o', 'udev', device]) for line in out: if 'ID_PART_ENTRY_TYPE=' in line: return line.split('=')[-1].strip() return ''
def plain_open(key, device, mapping): """ Decrypt (open) an encrypted device, previously prepared with cryptsetup in plain mode .. note: ceph-disk will require an additional b64decode call for this to work :param key: dmcrypt secret key :param device: absolute path to device :param mapping: mapping name used to correlate device. Usually a UUID """ command = [ 'cryptsetup', '--key-file', '-', '--allow-discards', # allow discards (aka TRIM) requests for device 'open', device, mapping, '--type', 'plain', '--key-size', '256', ] process.call(command, stdin=key, terminal_verbose=True, show_command=True)
def dmsetup_splitname(dev): """ Run ``dmsetup splitname`` and parse the results. .. warning:: This call does not ensure that the device is correct or that it exists. ``dmsetup`` will happily take a non existing path and still return a 0 exit status. """ command = [ 'dmsetup', 'splitname', '--noheadings', "--separator=';'", '--nameprefixes', dev ] out, err, rc = process.call(command) return _splitname_parser(out)
def set_tag(self, key, value): """ Set the key/value pair as an LVM tag. Does not "refresh" the values of the current object for its tags. Meant to be a "fire and forget" type of modification. **warning**: Altering tags on a PV has to be done ensuring that the device is actually the one intended. ``pv_name`` is *not* a persistent value, only ``pv_uuid`` is. Using ``pv_uuid`` is the best way to make sure the device getting changed is the one needed. """ # remove it first if it exists if self.tags.get(key): current_value = self.tags[key] tag = "%s=%s" % (key, current_value) process.call(['pvchange', '--deltag', tag, self.pv_name]) process.call( [ 'pvchange', '--addtag', '%s=%s' % (key, value), self.pv_name ] )
def device_family(device): """ Returns a list of associated devices. It assumes that ``device`` is a parent device. It is up to the caller to ensure that the device being used is a parent, not a partition. """ labels = ['NAME', 'PARTLABEL', 'TYPE'] command = ['lsblk', '-P', '-p', '-o', ','.join(labels), device] out, err, rc = process.call(command) devices = [] for line in out: devices.append(_lsblk_parser(line)) return devices
def osd_mkfs_filestore(osd_id, fsid, keyring): """ Create the files for the OSD to function. A normal call will look like: ceph-osd --cluster ceph --mkfs --mkkey -i 0 \ --monmap /var/lib/ceph/osd/ceph-0/activate.monmap \ --osd-data /var/lib/ceph/osd/ceph-0 \ --osd-journal /var/lib/ceph/osd/ceph-0/journal \ --osd-uuid 8d208665-89ae-4733-8888-5d3bfbeeec6c \ --keyring /var/lib/ceph/osd/ceph-0/keyring \ --setuser ceph --setgroup ceph """ path = '/var/lib/ceph/osd/%s-%s/' % (conf.cluster, osd_id) monmap = os.path.join(path, 'activate.monmap') journal = os.path.join(path, 'journal') system.chown(journal) system.chown(path) command = [ 'ceph-osd', '--cluster', conf.cluster, # undocumented flag, sets the `type` file to contain 'filestore' '--osd-objectstore', 'filestore', '--mkfs', '-i', osd_id, '--monmap', monmap, '--keyfile', '-', # goes through stdin '--osd-data', path, '--osd-journal', journal, '--osd-uuid', fsid, '--setuser', 'ceph', '--setgroup', 'ceph' ] process.call(command, stdin=keyring, terminal_verbose=True, show_command=True)
def osd_mkfs_filestore(osd_id, fsid, keyring): """ Create the files for the OSD to function. A normal call will look like: ceph-osd --cluster ceph --mkfs --mkkey -i 0 \ --monmap /var/lib/ceph/osd/ceph-0/activate.monmap \ --osd-data /var/lib/ceph/osd/ceph-0 \ --osd-journal /var/lib/ceph/osd/ceph-0/journal \ --osd-uuid 8d208665-89ae-4733-8888-5d3bfbeeec6c \ --keyring /var/lib/ceph/osd/ceph-0/keyring \ --setuser ceph --setgroup ceph """ path = '/var/lib/ceph/osd/%s-%s/' % (conf.cluster, osd_id) monmap = os.path.join(path, 'activate.monmap') journal = os.path.join(path, 'journal') system.chown(journal) system.chown(path) command = [ 'ceph-osd', '--cluster', conf.cluster, '--osd-objectstore', 'filestore', '--mkfs', '-i', osd_id, '--monmap', monmap, ] if __release__ != 'luminous': # goes through stdin command.extend(['--keyfile', '-']) command.extend([ '--osd-data', path, '--osd-journal', journal, '--osd-uuid', fsid, '--setuser', 'ceph', '--setgroup', 'ceph' ]) _, _, returncode = process.call( command, stdin=keyring, terminal_verbose=True, show_command=True ) if returncode != 0: raise RuntimeError('Command failed with exit code %s: %s' % (returncode, ' '.join(command)))
def get_api_lvs(): """ Return the list of logical volumes available in the system using flags to include common metadata associated with them Command and delimeted output, should look like:: $ lvs --noheadings --readonly --separator=';' -o lv_tags,lv_path,lv_name,vg_name ;/dev/ubuntubox-vg/root;root;ubuntubox-vg ;/dev/ubuntubox-vg/swap_1;swap_1;ubuntubox-vg """ fields = 'lv_tags,lv_path,lv_name,vg_name,lv_uuid' stdout, stderr, returncode = process.call( ['lvs', '--noheadings', '--readonly', '--separator=";"', '-o', fields] ) return _output_parser(stdout, fields)
def get_api_vgs(): """ Return the list of group volumes available in the system using flags to include common metadata associated with them Command and sample JSON output, should look like:: $ sudo vgs --reportformat=json { "report": [ { "vg": [ { "vg_name":"VolGroup00", "pv_count":"1", "lv_count":"2", "snap_count":"0", "vg_attr":"wz--n-", "vg_size":"38.97g", "vg_free":"0 "}, { "vg_name":"osd_vg", "pv_count":"3", "lv_count":"1", "snap_count":"0", "vg_attr":"wz--n-", "vg_size":"32.21g", "vg_free":"9.21g" } ] } ] } """ stdout, stderr, returncode = process.call( [ 'sudo', 'vgs', '--reportformat=json' ] ) report = json.loads(''.join(stdout)) for report_item in report.get('report', []): # is it possible to get more than one item in "report" ? return report_item['vg'] return []
def get_api_vgs(): """ Return the list of group volumes available in the system using flags to include common metadata associated with them Command and sample delimeted output, should look like:: $ vgs --noheadings --readonly --separator=';' \ -o vg_name,pv_count,lv_count,snap_count,vg_attr,vg_size,vg_free ubuntubox-vg;1;2;0;wz--n-;299.52g;12.00m osd_vg;3;1;0;wz--n-;29.21g;9.21g """ fields = 'vg_name,pv_count,lv_count,snap_count,vg_attr,vg_size,vg_free' stdout, stderr, returncode = process.call( ['vgs', '--noheadings', '--readonly', '--separator=";"', '-o', fields] ) return _output_parser(stdout, fields)
def status(device): """ Capture the metadata information of a possibly encrypted device, returning a dictionary with all the values found (if any). An encrypted device will contain information about a device. Example successful output looks like:: $ cryptsetup status /dev/mapper/ed6b5a26-eafe-4cd4-87e3-422ff61e26c4 /dev/mapper/ed6b5a26-eafe-4cd4-87e3-422ff61e26c4 is active and is in use. type: LUKS1 cipher: aes-xts-plain64 keysize: 256 bits device: /dev/sdc2 offset: 4096 sectors size: 20740063 sectors mode: read/write As long as the mapper device is in 'open' state, the ``status`` call will work. :param device: Absolute path or UUID of the device mapper """ command = [ 'cryptsetup', 'status', device, ] out, err, code = process.call(command, show_command=True, verbose_on_failure=False) metadata = {} if code != 0: logger.warning('failed to detect device mapper information') return metadata for line in out: # get rid of lines that might not be useful to construct the report: if not line.startswith(' '): continue try: column, value = line.split(': ') except ValueError: continue metadata[column.strip()] = value.strip().strip('"') return metadata
def remove_lv(path): """ Removes a logical volume given it's absolute path. Will return True if the lv is successfully removed or raises a RuntimeError if the removal fails. """ stdout, stderr, returncode = process.call( [ 'lvremove', '-v', # verbose '-f', # force it path ], show_command=True, terminal_verbose=True, ) if returncode != 0: raise RuntimeError("Unable to remove %s".format(path)) return True
def get_pvs(fields=PV_FIELDS, filters='', tags=None): """ Return a list of PVs that are available on the system and match the filters and tags passed. Argument filters takes a dictionary containing arguments required by -S option of LVM. Passing a list of LVM tags can be quite tricky to pass as a dictionary within dictionary, therefore pass dictionary of tags via tags argument and tricky part will be taken care of by the helper methods. :param fields: string containing list of fields to be displayed by the pvs command :param sep: string containing separator to be used between two fields :param filters: dictionary containing LVM filters :param tags: dictionary containng LVM tags :returns: list of class PVolume object representing pvs on the system """ filters = make_filters_lvmcmd_ready(filters, tags) args = ['pvs', '--no-heading', '--readonly', '--separator=";"', '-S', filters, '-o', fields] stdout, stderr, returncode = process.call(args, verbose_on_failure=False) pvs_report = _output_parser(stdout, fields) return [PVolume(**pv_report) for pv_report in pvs_report]
def get_vgs(fields=VG_FIELDS, filters='', tags=None): """ Return a list of VGs that are available on the system and match the filters and tags passed. Argument filters takes a dictionary containing arguments required by -S option of LVM. Passing a list of LVM tags can be quite tricky to pass as a dictionary within dictionary, therefore pass dictionary of tags via tags argument and tricky part will be taken care of by the helper methods. :param fields: string containing list of fields to be displayed by the vgs command :param sep: string containing separator to be used between two fields :param filters: dictionary containing LVM filters :param tags: dictionary containng LVM tags :returns: list of class VolumeGroup object representing vgs on the system """ filters = make_filters_lvmcmd_ready(filters, tags) args = ['vgs'] + VG_CMD_OPTIONS + ['-S', filters, '-o', fields] stdout, stderr, returncode = process.call(args, run_on_host=True, verbose_on_failure=False) vgs_report = _output_parser(stdout, fields) return [VolumeGroup(**vg_report) for vg_report in vgs_report]
def _validate_bluestore_device(device, excepted_device_type, osd_uuid): """ Validate whether the given device is truly what it is supposed to be """ out, err, ret = process.call(['ceph-bluestore-tool', 'show-label', '--dev', device]) if err: terminal.error('ceph-bluestore-tool failed to run. %s'% err) raise SystemExit(1) if ret: terminal.error('no label on %s'% device) raise SystemExit(1) oj = json.loads(''.join(out)) if device not in oj: terminal.error('%s not in the output of ceph-bluestore-tool, buggy?'% device) raise SystemExit(1) current_device_type = oj[device]['description'] if current_device_type != excepted_device_type: terminal.error('%s is not a %s device but %s'% (device, excepted_device_type, current_device_type)) raise SystemExit(1) current_osd_uuid = oj[device]['osd_uuid'] if current_osd_uuid != osd_uuid: terminal.error('device %s is used by another osd %s as %s, should be %s'% (device, current_osd_uuid, current_device_type, osd_uuid)) raise SystemExit(1)
def test_stdin(self): process.call(['xargs', 'ls'], stdin="echo '/'")
def lsblk_all(device='', columns=None, abspath=False): """ Create a dictionary of identifying values for a device using ``lsblk``. Each supported column is a key, in its *raw* format (all uppercase usually). ``lsblk`` has support for certain "columns" (in blkid these would be labels), and these columns vary between distributions and ``lsblk`` versions. The newer versions support a richer set of columns, while older ones were a bit limited. These are a subset of lsblk columns which are known to work on both CentOS 7 and Xenial: NAME device name KNAME internal kernel device name PKNAME internal kernel parent device name MAJ:MIN major:minor device number FSTYPE filesystem type MOUNTPOINT where the device is mounted LABEL filesystem LABEL UUID filesystem UUID RO read-only device RM removable device MODEL device identifier SIZE size of the device STATE state of the device OWNER user name GROUP group name MODE device node permissions ALIGNMENT alignment offset MIN-IO minimum I/O size OPT-IO optimal I/O size PHY-SEC physical sector size LOG-SEC logical sector size ROTA rotational device SCHED I/O scheduler name RQ-SIZE request queue size TYPE device type PKNAME internal parent kernel device name DISC-ALN discard alignment offset DISC-GRAN discard granularity DISC-MAX discard max bytes DISC-ZERO discard zeroes data There is a bug in ``lsblk`` where using all the available (supported) columns will result in no output (!), in order to workaround this the following columns have been removed from the default reporting columns: * RQ-SIZE (request queue size) * MIN-IO minimum I/O size * OPT-IO optimal I/O size These should be available however when using `columns`. For example:: >>> lsblk('/dev/sda1', columns=['OPT-IO']) {'OPT-IO': '0'} Normal CLI output, as filtered by the flags in this function will look like :: $ lsblk -P -o NAME,KNAME,PKNAME,MAJ:MIN,FSTYPE,MOUNTPOINT NAME="sda1" KNAME="sda1" MAJ:MIN="8:1" FSTYPE="ext4" MOUNTPOINT="/" :param columns: A list of columns to report as keys in its original form. :param abspath: Set the flag for absolute paths on the report """ default_columns = [ 'NAME', 'KNAME', 'PKNAME', 'MAJ:MIN', 'FSTYPE', 'MOUNTPOINT', 'LABEL', 'UUID', 'RO', 'RM', 'MODEL', 'SIZE', 'STATE', 'OWNER', 'GROUP', 'MODE', 'ALIGNMENT', 'PHY-SEC', 'LOG-SEC', 'ROTA', 'SCHED', 'TYPE', 'DISC-ALN', 'DISC-GRAN', 'DISC-MAX', 'DISC-ZERO', 'PKNAME', 'PARTLABEL' ] columns = columns or default_columns # -P -> Produce pairs of COLUMN="value" # -p -> Return full paths to devices, not just the names, when ``abspath`` is set # -o -> Use the columns specified or default ones provided by this function base_command = ['lsblk', '-P'] if abspath: base_command.append('-p') base_command.append('-o') base_command.append(','.join(columns)) out, err, rc = process.call(base_command) if rc != 0: raise RuntimeError(f"Error: {err}") result = [] for line in out: result.append(_lsblk_parser(line)) if not device: return result for dev in result: if dev['NAME'] == os.path.basename(device): return dev return {}
def test_unicode_encoding_stdin(self): process.call(['echo'], stdin=u'\xd0'.encode('utf-8'))
def test_unicode_encoding(self): process.call(['echo', u'\xd0'])
def clear_tag(self, key): if self.tags.get(key): current_value = self.tags[key] tag = "%s=%s" % (key, current_value) process.call(['lvchange', '--deltag', tag, self.lv_path]) del self.tags[key]
def deactivate(self): """ Deactivate the LV by calling lvchange -an """ process.call(['lvchange', '-an', self.lv_path])
def osd_mkfs_bluestore(osd_id, fsid, keyring=None, wal=False, db=False): """ Create the files for the OSD to function. A normal call will look like: ceph-osd --cluster ceph --mkfs --mkkey -i 0 \ --monmap /var/lib/ceph/osd/ceph-0/activate.monmap \ --osd-data /var/lib/ceph/osd/ceph-0 \ --osd-uuid 8d208665-89ae-4733-8888-5d3bfbeeec6c \ --keyring /var/lib/ceph/osd/ceph-0/keyring \ --setuser ceph --setgroup ceph In some cases it is required to use the keyring, when it is passed in as a keyword argument it is used as part of the ceph-osd command """ path = '/var/lib/ceph/osd/%s-%s/' % (conf.cluster, osd_id) monmap = os.path.join(path, 'activate.monmap') system.chown(path) base_command = [ 'ceph-osd', '--cluster', conf.cluster, '--osd-objectstore', 'bluestore', '--mkfs', '-i', osd_id, '--monmap', monmap, ] supplementary_command = [ '--osd-data', path, '--osd-uuid', fsid, '--setuser', 'ceph', '--setgroup', 'ceph' ] if keyring is not None: base_command.extend(['--keyfile', '-']) if wal: base_command.extend(['--bluestore-block-wal-path', wal]) system.chown(wal) if db: base_command.extend(['--bluestore-block-db-path', db]) system.chown(db) if get_osdspec_affinity(): base_command.extend(['--osdspec-affinity', get_osdspec_affinity()]) command = base_command + supplementary_command """ When running in containers the --mkfs on raw device sometimes fails to acquire a lock through flock() on the device because systemd-udevd holds one temporarily. See KernelDevice.cc and _lock() to understand how ceph-osd acquires the lock. Because this is really transient, we retry up to 5 times and wait for 1 sec in-between """ for retry in range(5): _, _, returncode = process.call(command, stdin=keyring, terminal_verbose=True, show_command=True) if returncode == 0: break else: if returncode == errno.EWOULDBLOCK: time.sleep(1) logger.info( 'disk is held by another process, trying to mkfs again... (%s/5 attempt)' % retry) continue else: raise RuntimeError('Command failed with exit code %s: %s' % (returncode, ' '.join(command)))
def generate(self, devs=None): if not devs: logger.debug('Listing block devices via lsblk...') devs = [] # adding '--inverse' allows us to get the mapper devices list in that command output. # not listing root devices containing partitions shouldn't have side effect since we are # in `ceph-volume raw` context. # # example: # running `lsblk --paths --nodeps --output=NAME --noheadings` doesn't allow to get the mapper list # because the output is like following : # # $ lsblk --paths --nodeps --output=NAME --noheadings # /dev/sda # /dev/sdb # /dev/sdc # /dev/sdd # # the dmcrypt mappers are hidden because of the `--nodeps` given they are displayed as a dependency. # # $ lsblk --paths --output=NAME --noheadings # /dev/sda # |-/dev/mapper/ceph-3b52c90d-6548-407d-bde1-efd31809702f-sda-block-dmcrypt # `-/dev/mapper/ceph-3b52c90d-6548-407d-bde1-efd31809702f-sda-db-dmcrypt # /dev/sdb # /dev/sdc # /dev/sdd # # adding `--inverse` is a trick to get around this issue, the counterpart is that we can't list root devices if they contain # at least one partition but this shouldn't be an issue in `ceph-volume raw` context given we only deal with raw devices. out, err, ret = process.call([ 'lsblk', '--paths', '--nodeps', '--output=NAME', '--noheadings', '--inverse' ]) assert not ret devs = out result = {} for dev in devs: logger.debug('Examining %s' % dev) # bluestore? out, err, ret = process.call( ['ceph-bluestore-tool', 'show-label', '--dev', dev], verbose_on_failure=False) if ret: logger.debug('No label on %s' % dev) continue oj = json.loads(''.join(out)) if dev not in oj: continue if oj[dev]['description'] != 'main': # ignore non-main devices, for now continue whoami = oj[dev]['whoami'] result[whoami] = { 'type': 'bluestore', 'osd_id': int(whoami), } for f in ['osd_uuid', 'ceph_fsid']: result[whoami][f] = oj[dev][f] result[whoami]['device'] = dev return result
def get_device_vgs(device, name_prefix=''): stdout, stderr, returncode = process.call(['pvs'] + VG_CMD_OPTIONS + ['-o', VG_FIELDS, device], verbose_on_failure=False) vgs = _output_parser(stdout, VG_FIELDS) return [VolumeGroup(**vg) for vg in vgs]
def get_device_lvs(device, name_prefix=''): stdout, stderr, returncode = process.call(['pvs'] + LV_CMD_OPTIONS + ['-o', LV_FIELDS, device], verbose_on_failure=False) lvs = _output_parser(stdout, LV_FIELDS) return [Volume(**lv) for lv in lvs]
def lsblk(device, columns=None): """ Create a dictionary of identifying values for a device using ``lsblk``. Each supported column is a key, in its *raw* format (all uppercase usually). ``lsblk`` has support for certain "columns" (in blkid these would be labels), and these columns vary between distributions and ``lsblk`` versions. The newer versions support a richer set of columns, while older ones were a bit limited. These are the default lsblk columns reported which are safe to use for Ubuntu 14.04.5 LTS: NAME device name KNAME internal kernel device name MAJ:MIN major:minor device number FSTYPE filesystem type MOUNTPOINT where the device is mounted LABEL filesystem LABEL UUID filesystem UUID RO read-only device RM removable device MODEL device identifier SIZE size of the device STATE state of the device OWNER user name GROUP group name MODE device node permissions ALIGNMENT alignment offset MIN-IO minimum I/O size OPT-IO optimal I/O size PHY-SEC physical sector size LOG-SEC logical sector size ROTA rotational device SCHED I/O scheduler name RQ-SIZE request queue size TYPE device type DISC-ALN discard alignment offset DISC-GRAN discard granularity DISC-MAX discard max bytes DISC-ZERO discard zeroes data There is a bug in ``lsblk`` where using all the available (supported) columns will result in no output (!), in order to workaround this the following columns have been removed from the default reporting columns: * RQ-SIZE (request queue size) * MIN-IO minimum I/O size * OPT-IO optimal I/O size These should be available however when using `columns`. For example:: >>> lsblk('/dev/sda1', columns=['OPT-IO']) {'OPT-IO': '0'} Normal CLI output, as filtered by the flags in this function will look like :: $ sudo lsblk --nodeps -P -o NAME,KNAME,MAJ:MIN,FSTYPE,MOUNTPOINT NAME="sda1" KNAME="sda1" MAJ:MIN="8:1" FSTYPE="ext4" MOUNTPOINT="/" :param columns: A list of columns to report as keys in its original form. """ default_columns = [ 'NAME', 'KNAME', 'MAJ:MIN', 'FSTYPE', 'MOUNTPOINT', 'LABEL', 'UUID', 'RO', 'RM', 'MODEL', 'SIZE', 'STATE', 'OWNER', 'GROUP', 'MODE', 'ALIGNMENT', 'PHY-SEC', 'LOG-SEC', 'ROTA', 'SCHED', 'TYPE', 'DISC-ALN', 'DISC-GRAN', 'DISC-MAX', 'DISC-ZERO' ] device = device.rstrip('/') columns = columns or default_columns # --nodeps -> Avoid adding children/parents to the device, only give information # on the actual device we are querying for # -P -> Produce pairs of COLUMN="value" # -o -> Use the columns specified or default ones provided by this function command = ['sudo', 'lsblk', '--nodeps', '-P', '-o'] command.append(','.join(columns)) command.append(device) out, err, rc = process.call(command) if rc != 0: return {} # parse the COLUMN="value" output to construct the dictionary pairs = ' '.join(out).split() parsed = {} for pair in pairs: try: column, value = pair.split('=') except ValueError: continue parsed[column] = value.strip().strip().strip('"') return parsed
def is_active(unit): out, err, rc = process.call(['systemctl', 'is-active', unit], verbose_on_failure=False) return rc == 0
def generate(self, devs=None): logger.debug('Listing block devices via lsblk...') # in case where we come from `ceph-volume raw activate` # `--device` will call `List.list()` with a string instead of a list # which can lead the logic in this function to a bug. The following lines will basically # convert it to a list with a single element to be sure we don't hit any issue. if isinstance(devs, str): devs = [devs] if devs is None or devs == []: devs = [] # If no devs are given initially, we want to list ALL devices including children and # parents. Parent disks with child partitions may be the appropriate device to return if # the parent disk has a bluestore header, but children may be the most appropriate # devices to return if the parent disk does not have a bluestore header. out, err, ret = process.call([ 'lsblk', '--paths', '--output=NAME', '--noheadings', '--list' ]) assert not ret devs = out result = {} logger.debug('inspecting devices: {}'.format(devs)) for dev in devs: info = disk.lsblk(dev, abspath=True) # Linux kernels built with CONFIG_ATARI_PARTITION enabled can falsely interpret # bluestore's on-disk format as an Atari partition table. These false Atari partitions # can be interpreted as real OSDs if a bluestore OSD was previously created on the false # partition. See https://tracker.ceph.com/issues/52060 for more info. If a device has a # parent, it is a child. If the parent is a valid bluestore OSD, the child will only # exist if it is a phantom Atari partition, and the child should be ignored. If the # parent isn't bluestore, then the child could be a valid bluestore OSD. If we fail to # determine whether a parent is bluestore, we should err on the side of not reporting # the child so as not to give a false negative. if 'PKNAME' in info and info['PKNAME'] != "": parent = info['PKNAME'] try: if disk.has_bluestore_label(parent): logger.warning(( 'ignoring child device {} whose parent {} is a BlueStore OSD.' .format(dev, parent), 'device is likely a phantom Atari partition. device info: {}' .format(info))) continue except OSError as e: logger.error(( 'ignoring child device {} to avoid reporting invalid BlueStore data from phantom Atari partitions.' .format(dev), 'failed to determine if parent device {} is BlueStore. err: {}' .format(parent, e))) continue bs_info = _get_bluestore_info(dev) if bs_info is None: # None is also returned in the rare event that there is an issue reading info from # a BlueStore disk, so be sure to log our assumption that it isn't bluestore logger.info( 'device {} does not have BlueStore information'.format( dev)) continue result[bs_info['osd_uuid']] = bs_info return result
def has_bluestore_label(self): out, err, ret = process.call( ['ceph-bluestore-tool', 'show-label', '--dev', self.path]) if ret: return False return True