diff --git a/Makefile b/Makefile index fd61d33cf..052e8fdb8 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ PYTHON=python3 +PKG_INSTALL?=dnf L10N_REPOSITORY=git@github.com:storaged-project/blivet-weblate.git L10N_BRANCH=master @@ -6,10 +7,15 @@ L10N_BRANCH=master PKGNAME=blivet SPECFILE=python-blivet.spec VERSION=$(shell $(PYTHON) setup.py --version) +RPMVERSION=$(shell rpmspec -q --queryformat "%{version}\n" $(SPECFILE) | head -1) +RPMRELEASE=$(shell rpmspec --undefine '%dist' -q --queryformat "%{release}\n" $(SPECFILE) | head -1) +RC_RELEASE ?= $(shell date -u +0.1.%Y%m%d%H%M%S) +RELEASE_TAG=$(PKGNAME)-$(RPMVERSION)-$(RPMRELEASE) VERSION_TAG=$(PKGNAME)-$(VERSION) COVERAGE=$(PYTHON) -m coverage +MOCKCHROOT ?= fedora-rawhide-$(shell uname -m) all: $(MAKE) -C po @@ -118,9 +124,17 @@ ChangeLog: (GIT_DIR=.git git log > .changelog.tmp && mv .changelog.tmp ChangeLog; rm -f .changelog.tmp) || (touch ChangeLog; echo 'git directory not found: installing possibly empty changelog.' >&2) tag: - tag='$(VERSION_TAG)' ; \ - git tag -a -s -m "Tag as $$tag" -f $$tag && \ - echo "Tagged as $$tag" + @if test $(VERSION) != $(RPMVERSION) ; then \ + tags='$(VERSION_TAG) $(RELEASE_TAG)' ; \ + elif test $(RPMRELEASE) = "1" ; then \ + tags='$(VERSION_TAG) $(RELEASE_TAG)' ; \ + else \ + tags='$(RELEASE_TAG)' ; \ + fi ; \ + for tag in $$tags ; do \ + git tag -a -s -m "Tag as $$tag" -f $$tag ; \ + echo "Tagged as $$tag" ; \ + done release: tag archive @@ -146,6 +160,10 @@ local: po-pull git ls-files tests/ | tar -T- -czf $(PKGNAME)-$(VERSION)-tests.tar.gz @echo "The test archive is in $(PKGNAME)-$(VERSION)-tests.tar.gz" +rpmlog: + @git log --pretty="format:- %s (%ae)" $(RELEASE_TAG).. |sed -e 's/@.*)/)/' + @echo + bumpver: po-pull @opts="-n $(PKGNAME) -v $(VERSION) -r $(RPMRELEASE)" ; \ if [ ! -z "$(IGNORE)" ]; then \ @@ -162,6 +180,38 @@ bumpver: po-pull fi ; \ ( scripts/makebumpver $${opts} ) || exit 1 ; \ +scratch-bumpver: + @opts="-n $(PKGNAME) -v $(RPMVERSION) -r $(RPMRELEASE) --newrelease $(RC_RELEASE)" ; \ + if [ ! -z "$(IGNORE)" ]; then \ + opts="$${opts} -i $(IGNORE)" ; \ + fi ; \ + if [ ! -z "$(MAP)" ]; then \ + opts="$${opts} -m $(MAP)" ; \ + fi ; \ + if [ ! -z "$(SKIP_ACKS)" ]; then \ + opts="$${opts} -s" ; \ + fi ; \ + if [ ! -z "$(BZDEBUG)" ]; then \ + opts="$${opts} -d" ; \ + fi ; \ + ( scripts/makebumpver $${opts} ) || exit 1 ; \ + +scratch: + @rm -f ChangeLog + @make ChangeLog + @rm -rf $(PKGNAME)-$(VERSION).tar.gz + @rm -rf /tmp/$(PKGNAME)-$(VERSION) /tmp/$(PKGNAME) + @dir=$$PWD; cp -a $$dir /tmp/$(PKGNAME)-$(VERSION) + @cd /tmp/$(PKGNAME)-$(VERSION) ; $(PYTHON) setup.py -q sdist + @cp /tmp/$(PKGNAME)-$(VERSION)/dist/$(PKGNAME)-$(VERSION).tar.gz . + @rm -rf /tmp/$(PKGNAME)-$(VERSION) + @echo "The archive is in $(PKGNAME)-$(VERSION).tar.gz" + +rc-release: scratch-bumpver scratch + mock -r $(MOCKCHROOT) --scrub all || exit 1 + mock -r $(MOCKCHROOT) --buildsrpm --spec ./$(SPECFILE) --sources . --resultdir $(PWD) || exit 1 + mock -r $(MOCKCHROOT) --rebuild *src.rpm --resultdir $(PWD) || exit 1 + srpm: local rpmbuild -bs --nodeps $(SPECFILE) --define "_sourcedir `pwd`" rm -f $(PKGNAME)-$(VERSION).tar.gz $(PKGNAME)-$(VERSION)-tests.tar.gz diff --git a/blivet/devices/stratis.py b/blivet/devices/stratis.py index 27644e669..4528dcd0c 100644 --- a/blivet/devices/stratis.py +++ b/blivet/devices/stratis.py @@ -129,8 +129,8 @@ def _set_passphrase(self, passphrase): @property def has_key(self): - return ((self.__passphrase not in ["", None]) or - (self._key_file and os.access(self._key_file, os.R_OK))) + return bool((self.__passphrase not in ["", None]) or + (self._key_file and os.access(self._key_file, os.R_OK))) def _pre_create(self): super(StratisPoolDevice, self)._pre_create() diff --git a/blivet/devicetree.py b/blivet/devicetree.py index 75e58c501..3f8304973 100644 --- a/blivet/devicetree.py +++ b/blivet/devicetree.py @@ -600,7 +600,7 @@ def get_device_by_id(self, id_num, incomplete=False, hidden=False): log_method_return(self, result) return result - def resolve_device(self, devspec, blkid_tab=None, crypt_tab=None, options=None): + def resolve_device(self, devspec, blkid_tab=None, crypt_tab=None, options=None, subvolspec=None): """ Return the device matching the provided device specification. The spec can be anything from a device name (eg: 'sda3') to a device @@ -615,6 +615,8 @@ def resolve_device(self, devspec, blkid_tab=None, crypt_tab=None, options=None): :type crypt_tab: :class:`~.CryptTab` :keyword options: mount options :type options: str + :keyword subvolspec: btrfs subvolume specification + :type subvolspec: str :returns: the device :rtype: :class:`~.devices.StorageDevice` or None """ @@ -727,26 +729,32 @@ def resolve_device(self, devspec, blkid_tab=None, crypt_tab=None, options=None): device = self.get_device_by_name(lv) # check mount options for btrfs volumes in case it's a subvol - if device and device.type.startswith("btrfs") and options: + if device and device.type.startswith("btrfs") and (subvolspec or options): # start with the volume -- not a subvolume device = getattr(device, "volume", device) - attr = None - if "subvol=" in options: - attr = "name" - val = util.get_option_value("subvol", options) - elif "subvolid=" in options: - attr = "vol_id" - val = util.get_option_value("subvolid", options) - elif device.default_subvolume: - # default subvolume - device = device.default_subvolume - - if attr and val: + if subvolspec: for subvol in device.subvolumes: - if getattr(subvol, attr, None) == val: + if subvol.format.subvolspec == subvolspec: device = subvol break + elif options: + attr = None + if "subvol=" in options: + attr = "name" + val = util.get_option_value("subvol", options) + elif "subvolid=" in options: + attr = "vol_id" + val = util.get_option_value("subvolid", options) + elif device.default_subvolume: + # default subvolume + device = device.default_subvolume + + if attr and val: + for subvol in device.subvolumes: + if getattr(subvol, attr, None) == val: + device = subvol + break if device: log.debug("resolved '%s' to '%s' (%s)", devspec, device.name, device.type) diff --git a/blivet/formats/luks.py b/blivet/formats/luks.py index 4aa15e763..ac854fbc2 100644 --- a/blivet/formats/luks.py +++ b/blivet/formats/luks.py @@ -212,8 +212,8 @@ def _set_passphrase(self, passphrase): @property def has_key(self): - return ((self.__passphrase not in ["", None]) or - (self._key_file and os.access(self._key_file, os.R_OK))) + return bool((self.__passphrase not in ["", None]) or + (self._key_file and os.access(self._key_file, os.R_OK))) @property def formattable(self): diff --git a/blivet/formats/stratis.py b/blivet/formats/stratis.py index dbb0528d8..6907e7113 100644 --- a/blivet/formats/stratis.py +++ b/blivet/formats/stratis.py @@ -112,8 +112,8 @@ def _set_passphrase(self, passphrase): @property def has_key(self): - return ((self.__passphrase not in ["", None]) or - (self._key_file and os.access(self._key_file, os.R_OK))) + return bool((self.__passphrase not in ["", None]) or + (self._key_file and os.access(self._key_file, os.R_OK))) def unlock_pool(self): if not self.locked_pool: diff --git a/blivet/iscsi.py b/blivet/iscsi.py index ed3c96a85..3b243f6fb 100644 --- a/blivet/iscsi.py +++ b/blivet/iscsi.py @@ -45,12 +45,12 @@ ISCSI_MODULES = ['cxgb3i', 'bnx2i', 'be2iscsi'] -STORAGED_SERVICE = "org.freedesktop.UDisks2" -STORAGED_PATH = "/org/freedesktop/UDisks2" -STORAGED_MANAGER_PATH = "/org/freedesktop/UDisks2/Manager" +UDISKS_SERVICE = "org.freedesktop.UDisks2" +UDISKS_PATH = "/org/freedesktop/UDisks2" +UDISKS_MANAGER_PATH = "/org/freedesktop/UDisks2/Manager" MANAGER_IFACE = "org.freedesktop.UDisks2.Manager" INITIATOR_IFACE = MANAGER_IFACE + ".ISCSI.Initiator" -SESSION_IFACE = STORAGED_SERVICE + ".ISCSI.Session" +SESSION_IFACE = UDISKS_SERVICE + ".ISCSI.Session" def has_iscsi(): @@ -89,7 +89,7 @@ def __init__(self, name, tpgt, address, port, iface): @property def conn_info(self): """The 5-tuple of connection info (no auth info). This form - is useful for interacting with storaged. + is useful for interacting with udisks. """ return (self.name, self.tpgt, self.address, self.port, self.iface) @@ -110,18 +110,17 @@ class iSCSIDependencyGuard(util.DependencyGuard): def _check_avail(self): try: - if not safe_dbus.check_object_available(STORAGED_SERVICE, STORAGED_MANAGER_PATH, MANAGER_IFACE): + if not safe_dbus.check_object_available(UDISKS_SERVICE, UDISKS_MANAGER_PATH, MANAGER_IFACE): return False - # storaged is modular and we need to make sure it has the iSCSI module - # loaded (this also autostarts storaged if it isn't running already) - safe_dbus.call_sync(STORAGED_SERVICE, STORAGED_MANAGER_PATH, MANAGER_IFACE, - "EnableModules", GLib.Variant("(b)", (True,))) + # Load the iscsi UDisks module (this also autostarts udisksd if needed) + safe_dbus.call_sync(UDISKS_SERVICE, UDISKS_MANAGER_PATH, MANAGER_IFACE, + "EnableModule", GLib.Variant("(sb)", ("iscsi", True,))) except safe_dbus.DBusCallError: return False - return safe_dbus.check_object_available(STORAGED_SERVICE, STORAGED_MANAGER_PATH, INITIATOR_IFACE) + return safe_dbus.check_object_available(UDISKS_SERVICE, UDISKS_MANAGER_PATH, INITIATOR_IFACE) -storaged_iscsi_required = iSCSIDependencyGuard() +udisks_iscsi_required = iSCSIDependencyGuard() class iSCSI(object): @@ -160,6 +159,11 @@ def __init__(self): self._initiator = initiatorname except Exception as e: # pylint: disable=broad-except log.info("failed to get initiator name from iscsi firmware: %s", str(e)) + else: + # write the firmware initiator to /etc/iscsi/initiatorname.iscsi + log.info("Setting up firmware iSCSI initiator name %s", self.initiator) + args = GLib.Variant("(sa{sv})", (initiatorname, None)) + self._call_initiator_method("SetInitiatorName", args) # So that users can write iscsi() to get the singleton instance def __call__(self): @@ -170,7 +174,7 @@ def __deepcopy__(self, memo_dict): return self @property - @storaged_iscsi_required(critical=False, eval_mode=util.EvalMode.onetime) + @udisks_iscsi_required(critical=False, eval_mode=util.EvalMode.onetime) def available(self): return True @@ -181,7 +185,7 @@ def _connection(self): return self.__connection - @storaged_iscsi_required(critical=True, eval_mode=util.EvalMode.onetime) + @udisks_iscsi_required(critical=True, eval_mode=util.EvalMode.onetime) def _call_initiator_method(self, method, args=None): """Class a method of the ISCSI.Initiator DBus object @@ -190,7 +194,7 @@ def _call_initiator_method(self, method, args=None): :type params: GLib.Variant """ - return safe_dbus.call_sync(STORAGED_SERVICE, STORAGED_MANAGER_PATH, + return safe_dbus.call_sync(UDISKS_SERVICE, UDISKS_MANAGER_PATH, INITIATOR_IFACE, method, args, connection=self._connection) @@ -200,7 +204,7 @@ def initiator_set(self): return self._initiator != "" @property - @storaged_iscsi_required(critical=False, eval_mode=util.EvalMode.onetime) + @udisks_iscsi_required(critical=False, eval_mode=util.EvalMode.onetime) def initiator(self): if self._initiator != "": return self._initiator @@ -210,7 +214,7 @@ def initiator(self): return raw_initiator.decode("utf-8", errors="replace") @initiator.setter - @storaged_iscsi_required(critical=True, eval_mode=util.EvalMode.onetime) + @udisks_iscsi_required(critical=True, eval_mode=util.EvalMode.onetime) def initiator(self, val): if len(val) == 0: raise ValueError(_("Must provide an iSCSI initiator name")) @@ -273,16 +277,16 @@ def _login(self, node_info, extra=None): if extra is None: extra = dict() extra["node.startup"] = GLib.Variant("s", "automatic") - extra["node.session.auth.chap_algs"] = GLib.Variant("s", "SHA3-256,SHA256,SHA1,MD5") + extra["node.session.auth.chap_algs"] = GLib.Variant("s", "SHA1,MD5") args = GLib.Variant("(sisisa{sv})", node_info.conn_info + (extra,)) self._call_initiator_method("Login", args) - @storaged_iscsi_required(critical=False, eval_mode=util.EvalMode.onetime) + @udisks_iscsi_required(critical=False, eval_mode=util.EvalMode.onetime) def _get_active_sessions(self): try: - objects = safe_dbus.call_sync(STORAGED_SERVICE, - STORAGED_PATH, + objects = safe_dbus.call_sync(UDISKS_SERVICE, + UDISKS_PATH, 'org.freedesktop.DBus.ObjectManager', 'GetManagedObjects', None)[0] @@ -303,11 +307,15 @@ def _get_active_sessions(self): return active - @storaged_iscsi_required(critical=False, eval_mode=util.EvalMode.onetime) + @udisks_iscsi_required(critical=False, eval_mode=util.EvalMode.onetime) def _start_ibft(self): if not flags.ibft: return + # Make sure iscsi_ibft is loaded otherwise any atttempts will fail with + # 'Could not get list of targets from firmware. (err 21)' + util.run_program(['modprobe', '-a', 'iscsi_ibft']) + args = GLib.Variant("(a{sv})", ([], )) try: found_nodes, _n_nodes = self._call_initiator_method("DiscoverFirmware", args) diff --git a/tests/storage_tests/iscsi_test.py b/tests/storage_tests/iscsi_test.py index 335b05d5e..714183c26 100644 --- a/tests/storage_tests/iscsi_test.py +++ b/tests/storage_tests/iscsi_test.py @@ -77,21 +77,17 @@ def create_iscsi_target(fpath, initiator_name=None): _delete_backstore(store_name) raise RuntimeError("Failed to create a new iscsi target") - if initiator_name: - status = subprocess.call(["targetcli", "/iscsi/%s/tpg1/acls create %s" % (iqn, initiator_name)], stdout=subprocess.DEVNULL) - if status != 0: - delete_iscsi_target(iqn, store_name) - raise RuntimeError("Failed to set ACLs for '%s'" % iqn) - with udev_settle(): status = subprocess.call(["targetcli", "/iscsi/%s/tpg1/luns create /backstores/fileio/%s" % (iqn, store_name)], stdout=subprocess.DEVNULL) if status != 0: delete_iscsi_target(iqn, store_name) raise RuntimeError("Failed to create a new LUN for '%s' using '%s'" % (iqn, store_name)) - status = subprocess.call(["targetcli", "/iscsi/%s/tpg1 set attribute generate_node_acls=1" % iqn], stdout=subprocess.DEVNULL) - if status != 0: - raise RuntimeError("Failed to set ACLs for '%s'" % iqn) + if initiator_name: + status = subprocess.call(["targetcli", "/iscsi/%s/tpg1/acls create %s" % (iqn, initiator_name)], stdout=subprocess.DEVNULL) + if status != 0: + delete_iscsi_target(iqn, store_name) + raise RuntimeError("Failed to set ACLs for '%s'" % iqn) return iqn, store_name @@ -130,6 +126,7 @@ def test_discover_login(self): if not has_iscsi() or not iscsi.available: self.skipTest("iSCSI not available, skipping") + # initially set the initiator to the correct/allowed one iscsi.initiator = self.initiator nodes = iscsi.discover("127.0.0.1") self.assertTrue(nodes) @@ -141,11 +138,28 @@ def test_discover_login(self): self.assertEqual(nodes[0].port, 3260) self.assertEqual(nodes[0].name, self.dev) - # change the initiator name + # change the initiator name to a wrong one iscsi.initiator = self.initiator + "_1" self.assertEqual(iscsi.initiator, self.initiator + "_1") - # try to login + # check the change made it to /etc/iscsi/initiatorname.iscsi + initiator_file = read_file("/etc/iscsi/initiatorname.iscsi").strip() + self.assertEqual(initiator_file, "InitiatorName=%s" % self.initiator + "_1") + + # try to login (should fail) + ret, err = iscsi.log_into_node(nodes[0]) + self.assertFalse(ret) + self.assertIn("authorization failure", err) + + # change the initiator name back to the correct one + iscsi.initiator = self.initiator + self.assertEqual(iscsi.initiator, self.initiator) + + # check the change made it to /etc/iscsi/initiatorname.iscsi + initiator_file = read_file("/etc/iscsi/initiatorname.iscsi").strip() + self.assertEqual(initiator_file, "InitiatorName=%s" % self.initiator) + + # try to login (should work now) ret, err = iscsi.log_into_node(nodes[0]) self.assertTrue(ret, "Login failed: %s" % err) diff --git a/tests/unit_tests/devicetree_test.py b/tests/unit_tests/devicetree_test.py index a6e909fe1..8d26d5807 100644 --- a/tests/unit_tests/devicetree_test.py +++ b/tests/unit_tests/devicetree_test.py @@ -10,6 +10,7 @@ from blivet.errors import DeviceTreeError, DuplicateUUIDError, InvalidMultideviceSelection from blivet.deviceaction import ACTION_TYPE_DESTROY, ACTION_OBJECT_DEVICE from blivet.devicelibs import lvm +from blivet.devices import BTRFSSubVolumeDevice, BTRFSVolumeDevice from blivet.devices import DiskDevice from blivet.devices import LVMVolumeGroupDevice from blivet.devices import LVMLogicalVolumeDevice @@ -68,6 +69,49 @@ def test_resolve_device(self): self.assertEqual(dt.resolve_device(dev3.name), dev3) self.assertEqual(dt.resolve_device(dev4.name), dev4) + def test_resolve_device_btrfs(self): + dt = DeviceTree() + + # same uuid for both volume and subvolume + btrfs_uuid = "1234-56-7890" + + dev = StorageDevice("deva", exists=True, + fmt=get_format("btrfs", uuid=btrfs_uuid, exists=True), + size=get_format("btrfs").min_size) + dt._add_device(dev) + + vol = BTRFSVolumeDevice("vol", exists=True, + parents=[dev], + fmt=get_format("btrfs", uuid=btrfs_uuid, exists=True)) + dt._add_device(vol) + + sub1 = BTRFSSubVolumeDevice("sub1", exists=True, + parents=[vol], + fmt=get_format("btrfs", options="subvol=sub1", + uuid=btrfs_uuid, exists=True, + subvolspec="sub1")) + dt._add_device(sub1) + + sub2 = BTRFSSubVolumeDevice("sub2", exists=True, + parents=[vol], + fmt=get_format("btrfs", options="subvol=sub2", + uuid=btrfs_uuid, exists=True, + subvolspec="sub2")) + dt._add_device(sub2) + + # resolve with name should work as usual + self.assertEqual(dt.resolve_device(vol.name), vol) + self.assertEqual(dt.resolve_device(sub1.name), sub1) + self.assertEqual(dt.resolve_device(sub2.name), sub2) + + # resolve by UUID with subvolspec + self.assertEqual(dt.resolve_device("UUID=%s" % btrfs_uuid, subvolspec=sub1.format.subvolspec), sub1) + self.assertEqual(dt.resolve_device("UUID=%s" % btrfs_uuid, subvolspec=sub2.format.subvolspec), sub2) + + # resolve by UUID with options + self.assertEqual(dt.resolve_device("UUID=%s" % btrfs_uuid, options="subvol=%s" % sub1.name), sub1) + self.assertEqual(dt.resolve_device("UUID=%s" % btrfs_uuid, options="subvol=%s" % sub2.name), sub2) + def test_device_name(self): # check that devicetree.names property contains all device's names diff --git a/translation-canary/README.rst b/translation-canary/README.rst index 8128f99de..ef0f00a48 100644 --- a/translation-canary/README.rst +++ b/translation-canary/README.rst @@ -35,3 +35,30 @@ warnings should be addressed instead of silently ignored as they scroll by in the build output. To use the script in a package that uses the gettext template files from autopoint or gettextize, set XGETTEXT=/path/to/xgettext_werror.sh in Makevars. + + +Usage as a subtree +================== + +Anaconda and Blivet use translation canary as a git subtree. If you need to +change the canary for these projects, follow these steps: + +1. Make changes and test them in the context of your project. + +2. Copy these changes to the ``translation-canary`` repo and make a pull + request with these changes. + +3. After that PR is merged, go to Anaconda or Blivet repo root and run:: + + dnf install git-subtree + git subtree pull --prefix translation-canary/ git@github.com:rhinstaller/translation-canary.git master --squash + + This produces a squash commit with some ancient parent, and then also + a merge commit that merges the squash commit into your current ``HEAD``. + +4. Make a topic branch and PR for merging what you got into your project. For + the purpose of making branches and PRs, the commits above are not special + in any way. + +Note: There is no way to detect subtree presence, it behaves as regular files +and directories. diff --git a/translation-canary/translation_canary/translatable/__init__.py b/translation-canary/translation_canary/translatable/__init__.py index f7f9ef9b9..15ead60c6 100644 --- a/translation-canary/translation_canary/translatable/__init__.py +++ b/translation-canary/translation_canary/translatable/__init__.py @@ -36,6 +36,8 @@ # Gather tests from this directory import pkgutil +import importlib + _tests = [] for finder, mod_name, _ispkg in pkgutil.iter_modules(__path__): # Skip __main__ @@ -43,7 +45,8 @@ continue # Load the module - module = finder.find_module(mod_name).load_module() + full_name = "{}.{}".format(__name__, mod_name) + module = importlib.import_module(full_name) # Look for attributes that start with 'test_' and add them to the test list for attrname, attr in module.__dict__.items(): diff --git a/translation-canary/translation_canary/translated/__init__.py b/translation-canary/translation_canary/translated/__init__.py index b962c2afa..c4e269f16 100644 --- a/translation-canary/translation_canary/translated/__init__.py +++ b/translation-canary/translation_canary/translated/__init__.py @@ -34,13 +34,16 @@ # Gather tests from this directory import pkgutil +import importlib + for finder, mod_name, _ispkg in pkgutil.iter_modules(__path__): # Skip __main__ if mod_name == "__main__": continue # Load the module - module = finder.find_module(mod_name).load_module(mod_name) + full_name = "{}.{}".format(__name__, mod_name) + module = importlib.import_module(full_name) # Look for attributes that start with 'test_' and add them to the test list for attrname, attr in module.__dict__.items():