summaryrefslogtreecommitdiff
path: root/cinder/privsep
diff options
context:
space:
mode:
authorZuul <zuul@review.opendev.org>2023-01-16 19:01:18 +0000
committerGerrit Code Review <review@openstack.org>2023-01-16 19:01:18 +0000
commitbc9910f0409c60d82788b9ad248327b377439c3a (patch)
tree23769eb1c16f2107b69a335e92e780c1b446edf1 /cinder/privsep
parent351470d1b07a3c0ce71c8a9f13d567dc00d70f00 (diff)
parent00793ac09ba125c6af43bbbfeadc483de042815b (diff)
downloadcinder-bc9910f0409c60d82788b9ad248327b377439c3a.tar.gz
Merge "LVM-nvmet: Use nvmetcli as library instead of CLI"
Diffstat (limited to 'cinder/privsep')
-rw-r--r--cinder/privsep/nvmcli.py41
-rw-r--r--cinder/privsep/path.py15
-rw-r--r--cinder/privsep/targets/nvmet.py209
3 files changed, 209 insertions, 56 deletions
diff --git a/cinder/privsep/nvmcli.py b/cinder/privsep/nvmcli.py
deleted file mode 100644
index 95f5b1c56..000000000
--- a/cinder/privsep/nvmcli.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright 2018 Red Hat, Inc
-# Copyright 2017 Rackspace Australia
-# Copyright 2018 Michael Still and Aptira
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-"""
-Helpers for nvmetcli related routines.
-"""
-
-from oslo_concurrency import processutils
-
-import cinder.privsep
-
-
-@cinder.privsep.sys_admin_pctxt.entrypoint
-def restore(path):
- cmd = [
- 'nvmetcli',
- 'restore',
- path]
- return processutils.execute(*cmd)
-
-
-@cinder.privsep.sys_admin_pctxt.entrypoint
-def save(path):
- cmd = [
- 'nvmetcli',
- 'save',
- path]
- return processutils.execute(*cmd)
diff --git a/cinder/privsep/path.py b/cinder/privsep/path.py
index 8ab98e54c..6fd7405fd 100644
--- a/cinder/privsep/path.py
+++ b/cinder/privsep/path.py
@@ -25,21 +25,6 @@ import cinder.privsep
@cinder.privsep.sys_admin_pctxt.entrypoint
-def readfile(path):
- if not os.path.exists(path):
- raise exception.FileNotFound(file_path=path)
- with open(path, 'r') as f:
- return f.read()
-
-
-@cinder.privsep.sys_admin_pctxt.entrypoint
-def removefile(path):
- if not os.path.exists(path):
- raise exception.FileNotFound(file_path=path)
- os.unlink(path)
-
-
-@cinder.privsep.sys_admin_pctxt.entrypoint
def touch(path):
if os.path.exists(path):
os.utime(path, None)
diff --git a/cinder/privsep/targets/nvmet.py b/cinder/privsep/targets/nvmet.py
new file mode 100644
index 000000000..9b2f5c8f7
--- /dev/null
+++ b/cinder/privsep/targets/nvmet.py
@@ -0,0 +1,209 @@
+# Copyright 2022 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""NVMet Python Interface using privsep
+
+This file adds the privsep support to the nvmet package so it can be easily
+consumed by Cinder nvmet target.
+
+It also:
+
+- Adds some methods to the Root class to be able to get a specific subsystem or
+ port directly without having to go through all the existing ones.
+
+- Presents the CFSNotFound exception as a NotFound exception which is easier to
+ consume.
+"""
+import os
+
+import nvmet
+from oslo_log import log as logging
+
+from cinder import exception
+from cinder import privsep
+
+
+LOG = logging.getLogger(__name__)
+
+
+###################
+# Helper methods to serialize/deserialize parameters to be sent through privsep
+# and to do the instance/class calls on the privsep side.
+
+def serialize(instance):
+ """Serialize parameters, specially nvmet instances.
+
+ The idea is to be able to pass an nvmet instance to privsep methods, since
+ they are sometimes required as parameters (ie: port.setup) and also to pass
+ the instance where do_privsep_call has to call a specific method.
+
+ Instances are passed as a tuple, with the name of the class as the first
+ element, and in the second element the kwargs necessary to instantiate the
+ instance of that class.
+
+ To differentiate nvmet instances from tuples there is a 'tuple' value that
+ can be passed in the first element of the tuple to differentiate them.
+
+ All other instances as passed unaltered.
+ """
+ if isinstance(instance, nvmet.Root):
+ return ('Root', {})
+
+ if isinstance(instance, (nvmet.Subsystem, nvmet.Host)):
+ return (type(instance).__name__, {'nqn': instance.nqn,
+ 'mode': 'lookup'})
+
+ if isinstance(instance, nvmet.Namespace):
+ return ('Namespace', {'nsid': instance.nsid,
+ 'subsystem': serialize(instance.subsystem),
+ 'mode': 'lookup'})
+
+ if isinstance(instance, nvmet.Port):
+ return ('Port', {'portid': instance.portid, 'mode': 'lookup'})
+
+ if isinstance(instance, nvmet.Referral):
+ return ('Referral', {'name': instance.name,
+ 'port': serialize(instance.port),
+ 'mode': 'lookup'})
+
+ if isinstance(instance, nvmet.ANAGroup):
+ return ('ANAGroup', {'grpid': instance.grpid,
+ 'port': serialize(instance.port),
+ 'mode': 'lookup'})
+
+ if isinstance(instance, tuple):
+ return ('tuple', instance)
+
+ return instance
+
+
+def deserialize(data):
+ """Deserialize an instance, specially nvmet instances.
+
+ Reverse operation of the serialize method. Converts an nvmet instance
+ serialized in a tuple into an actual nvmet instance.
+ """
+ if not isinstance(data, tuple):
+ return data
+
+ cls_name, cls_params = data
+ if cls_name == 'tuple':
+ return cls_params
+
+ # Parameters for the instantiation of the class can be nvmet objects
+ # themselves.
+ params = {name: deserialize(value) for name, value in cls_params.items()}
+ # We don't want the classes from the nvmet method but ours instead
+ instance = getattr(nvmet, cls_name)(**params)
+ return instance
+
+
+def deserialize_params(args, kwargs):
+ """Deserialize function arguments using deserialize method."""
+ args = [deserialize(arg) for arg in args]
+ kwargs = {key: deserialize(value) for key, value in kwargs.items()}
+ return args, kwargs
+
+
+def _nvmet_setup_failure(message):
+ """Simple error method to use when calling nvmet setup methods."""
+ LOG.error(message)
+ raise exception.CinderException(message)
+
+
+@privsep.sys_admin_pctxt.entrypoint
+def do_privsep_call(instance, method_name, *args, **kwargs):
+ """General privsep method for instance calls.
+
+ Handle privsep method calls by deserializing the instance where we want to
+ call a given method with the deserialized parameters.
+ """
+ LOG.debug('Calling %s on %s with %s - %s',
+ method_name, instance, args, kwargs)
+ instance = deserialize(instance)
+ method = getattr(instance, method_name)
+ args, kwargs = deserialize_params(args, kwargs)
+ # NOTE: No returning nvmet objects support. If needed add serialization on
+ # the result and deserialization decorator before the entrypoint.
+ return method(*args, **kwargs)
+
+
+@privsep.sys_admin_pctxt.entrypoint
+def privsep_setup(cls_name, *args, **kwargs):
+ """Special privsep method for nvmet setup method calls.
+
+ The setup method is a special case because it's a class method (which
+ privsep cannot handle) and also requires a function for the error handling.
+
+ This method accepts a class name and reconstructs it, then calls the
+ class' setup method passing our own error function.
+ """
+ LOG.debug('Setup %s with %s - %s', cls_name, args, kwargs)
+ cls = getattr(nvmet, cls_name)
+ args, kwargs = deserialize_params(args, kwargs)
+ kwargs['err_func'] = _nvmet_setup_failure
+ cls.setup(*args, **kwargs)
+
+
+###################
+# Classes that don't currently have privsep support
+
+Namespace = nvmet.Namespace
+Host = nvmet.Host
+Referral = nvmet.Referral
+ANAGroup = nvmet.ANAGroup
+
+
+###################
+# Custom classes that divert privileges calls to privsep
+# Support in these classes is limited to what's needed by the nvmet target.
+
+# Convenience error class link to nvmet's
+NotFound = nvmet.nvme.CFSNotFound
+
+
+class Subsystem(nvmet.Subsystem):
+ def __init__(self, nqn=None, mode='lookup'):
+ super().__init__(nqn=nqn, mode=mode)
+
+ @classmethod
+ def setup(cls, t):
+ privsep_setup(cls.__name__, t)
+
+ def delete(self):
+ do_privsep_call(serialize(self), 'delete')
+
+
+class Port(nvmet.Port):
+ def __init__(self, portid, mode='lookup'):
+ super().__init__(portid=portid, mode=mode)
+
+ @classmethod
+ def setup(cls, root, n):
+ privsep_setup(cls.__name__, serialize(root), n)
+
+ def add_subsystem(self, nqn):
+ do_privsep_call(serialize(self), 'add_subsystem', nqn)
+
+ def remove_subsystem(self, nqn):
+ do_privsep_call(serialize(self), 'remove_subsystem', nqn)
+
+ def delete(self):
+ do_privsep_call(serialize(self), 'delete')
+
+
+class Root(nvmet.Root):
+ @property
+ def ports(self):
+ for d in os.listdir(self.path + '/ports/'):
+ yield Port(os.path.basename(d))