From d0bd3e385122371bcfaa9c50fd4066e86ef928d7 Mon Sep 17 00:00:00 2001 From: Silvan Kaiser Date: Tue, 11 Sep 2018 15:21:09 +0200 Subject: [PATCH] Adding pike release and restructuring README files. --- full_quobyte_patch/Ocata/README.md | 23 + full_quobyte_patch/Pike/README.md | 19 + .../Pike/full_quobyte_pike_manila.patch | 124 ++++++ ...full_quobyte_pike_manila_driver.jsonrpc.py | 122 ++++++ ...full_quobyte_pike_manila_driver.quobyte.py | 401 ++++++++++++++++++ ...full_quobyte_pike_manila_full_source.patch | 168 ++++++++ .../Pike/full_quobyte_pike_nova.patch | 34 ++ .../full_quobyte_pike_nova_driver.quobyte.py | 169 ++++++++ .../full_quobyte_pike_nova_full_source.patch | 94 ++++ full_quobyte_patch/README.md | 46 +- 10 files changed, 1167 insertions(+), 33 deletions(-) create mode 100644 full_quobyte_patch/Ocata/README.md create mode 100644 full_quobyte_patch/Pike/README.md create mode 100644 full_quobyte_patch/Pike/full_quobyte_pike_manila.patch create mode 100644 full_quobyte_patch/Pike/full_quobyte_pike_manila_driver.jsonrpc.py create mode 100644 full_quobyte_patch/Pike/full_quobyte_pike_manila_driver.quobyte.py create mode 100644 full_quobyte_patch/Pike/full_quobyte_pike_manila_full_source.patch create mode 100644 full_quobyte_patch/Pike/full_quobyte_pike_nova.patch create mode 100644 full_quobyte_patch/Pike/full_quobyte_pike_nova_driver.quobyte.py create mode 100644 full_quobyte_patch/Pike/full_quobyte_pike_nova_full_source.patch diff --git a/full_quobyte_patch/Ocata/README.md b/full_quobyte_patch/Ocata/README.md new file mode 100644 index 0000000..af69fef --- /dev/null +++ b/full_quobyte_patch/Ocata/README.md @@ -0,0 +1,23 @@ +## Ocata Release +Fixes the following issues: + +- Cinder + - Fixes bug [1685277](https://bugs.launchpad.net/cinder/+bug/1685277) with patch [458885](https://review.openstack.org/#/c/458885/) in order to enable the usage of the Cinder Generic Volume Cache with the Quobyte driver (_this fix is only applied for the Ocata release_). + - Fixes bug [1674611](https://bugs.launchpad.net/cinder/+bug/1674611) with patch [447958](https://review.openstack.org/#/c/447958/), thus allowing e.g. cloned backups of Quobyte Cinder volumes. + - Fixes bug [1675710](https://bugs.launchpad.net/cinder/+bug/1675710) with patch [449553](https://review.openstack.org/#/c/449553/) in order to allow specifying multiple Quobyte registries in the quobyte_volume_url Cinder configuration option. + - Fixes bug [1687048](https://bugs.launchpad.net/cinder/+bug/1687048) with patch [461471](https://review.openstack.org/#/c/461471/) + - Removes the requirement to support extended attributes from Cinder mounts with patch [427833](https://review.openstack.org/#/c/427833/) for improved performance + +- Manila + - Fixes bug [1733807](https://bugs.launchpad.net/manila/+bug/1733807) with patch [570741](https://review.openstack.org/#/c/570741) for improved API compatibility. + - Fixes bug [1771958](https://bugs.launchpad.net/manila/+bug/1771958) with patch [569355](https://review.openstack.org/#/c/569355/) to fix initial quota creation. + - Fixes bug [1771970](https://bugs.launchpad.net/manila/+bug/1771970) with patch [569355](https://review.openstack.org/#/c/569355/) to correct the quota when resizing a Manila share. + - Fixes bug [1774604](https://bugs.launchpad.net/manila/+bug/1774604) with patch [571693](https://review.openstack.org/#/c/571693/) to avoid incoherent volume specification in resizing. + - Fixes bug [1773929](https://bugs.launchpad.net/manila/+bug/1773929) with patch [572291](https://review.openstack.org/#/c/572291/) to fix a coding style issue. + +- Nova + - Fixes bug [1530860](https://bugs.launchpad.net/nova/+bug/1530860) with patch [432344](https://review.openstack.org/#/c/432344/) and bug [1756823](https://bugs.launchpad.net/nova/+bug/1756823) with patch [554195](https://review.openstack.org/#/c/554195/) in order to prevent systemd service restarts from unmounting Nova mounts + - Fixes bug [1679976](https://bugs.launchpad.net/nova/+bug/1679976) with patch [453537](https://review.openstack.org/#/c/453537/) disallowing a specific and potentially bad exit code during volume mounts + - Removes the requirement to support extended attributes from Nova mounts with patch [428646](https://review.openstack.org/#/c/428646/) for improved performance + +The patches come in two versions, the driver only and the full source patch. diff --git a/full_quobyte_patch/Pike/README.md b/full_quobyte_patch/Pike/README.md new file mode 100644 index 0000000..1991e71 --- /dev/null +++ b/full_quobyte_patch/Pike/README.md @@ -0,0 +1,19 @@ +## Pike Release +Fixes the following issues: + +- Manila + - Fixes bug [1774604](https://bugs.launchpad.net/manila/+bug/1774604) with patch [571693](https://review.openstack.org/#/c/571693/) to avoid incoherent volume specification in resizing. + - Fixes bug [1773929](https://bugs.launchpad.net/manila/+bug/1773929) with patch [572291](https://review.openstack.org/#/c/572291/) to fix a coding style issue. + +- Nova + - Fixes bug [1756823](https://bugs.launchpad.net/nova/+bug/1756823) with a backported variant of patch [554195](https://review.openstack.org/#/c/554195/) in order to prevent systemd service restarts from unmounting Nova mounts + - NOTE: Please add the following config to */etc/nova/rootwrap.d/compute.filters* when using the patched Nova setup: + + + ``` + # nova/virt/libvirt/volume/quobyte.py + systemd-run: CommandFilter, systemd-run, root + ``` + + +The patches come in three versions, the driver only patch, the full source patch and the full driver file. diff --git a/full_quobyte_patch/Pike/full_quobyte_pike_manila.patch b/full_quobyte_patch/Pike/full_quobyte_pike_manila.patch new file mode 100644 index 0000000..e400981 --- /dev/null +++ b/full_quobyte_patch/Pike/full_quobyte_pike_manila.patch @@ -0,0 +1,124 @@ +diff --git a/manila/share/drivers/quobyte/jsonrpc.py b/manila/share/drivers/quobyte/jsonrpc.py +index ba4b60ec..df752802 100644 +--- a/manila/share/drivers/quobyte/jsonrpc.py ++++ b/manila/share/drivers/quobyte/jsonrpc.py +@@ -59,7 +59,9 @@ class JsonRpc(object): + self._cert_file = cert_file + + @utils.synchronized('quobyte-request') +- def call(self, method_name, user_parameters, expected_errors=[]): ++ def call(self, method_name, user_parameters, expected_errors=None): ++ if expected_errors is None: ++ expected_errors = [] + # prepare request + self._id += 1 + parameters = {'retry': 'INFINITELY'} # Backend specific setting +@@ -103,7 +105,9 @@ class JsonRpc(object): + LOG.debug("Backend request resulted in error: %s", result.text) + result.raise_for_status() + +- def _checked_for_application_error(self, result, expected_errors=[]): ++ def _checked_for_application_error(self, result, expected_errors=None): ++ if expected_errors is None: ++ expected_errors = [] + if 'error' in result and result['error']: + if 'message' in result['error'] and 'code' in result['error']: + if result["error"]["code"] in expected_errors: +diff --git a/manila/share/drivers/quobyte/quobyte.py b/manila/share/drivers/quobyte/quobyte.py +index 55d7eeb9..1b21ac24 100644 +--- a/manila/share/drivers/quobyte/quobyte.py ++++ b/manila/share/drivers/quobyte/quobyte.py +@@ -78,9 +78,10 @@ class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,): + 1.2.3 - Updated RPC layer for improved stability + 1.2.4 - Fixed handling updated QB API error codes + 1.2.5 - Fixed two quota handling bugs ++ 1.2.6 - Fixed volume resize and jsonrpc code style bugs + """ + +- DRIVER_VERSION = '1.2.5' ++ DRIVER_VERSION = '1.2.6' + + def __init__(self, *args, **kwargs): + super(QuobyteShareDriver, self).__init__(False, *args, **kwargs) +@@ -89,9 +90,8 @@ class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,): + or CONF.share_backend_name or 'Quobyte') + + def _fetch_existing_access(self, context, share): +- volume_uuid = self._resolve_volume_name( +- share['name'], +- self._get_project_name(context, share['project_id'])) ++ volume_uuid = self._resolve_volume_name(share['name'], ++ share['project_id']) + result = self.rpc.call('getConfiguration', {}) + if result is None: + raise exception.QBException( +@@ -184,7 +184,8 @@ class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,): + self.rpc.call('setQuota', {"quotas": [ + {"consumer": + [{"type": "VOLUME", +- "identifier": share["name"], ++ "identifier": self._resolve_volume_name(share["name"], ++ share['project_id']), + "tenant_id": share["project_id"]}], + "limits": [{"type": "LOGICAL_DISK_SPACE", + "value": newsize_bytes}]} +@@ -223,9 +224,8 @@ class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,): + raise exception.QBException( + _('Quobyte driver only supports NFS shares')) + +- volume_uuid = self._resolve_volume_name( +- share['name'], +- self._get_project_name(context, share['project_id'])) ++ volume_uuid = self._resolve_volume_name(share['name'], ++ share['project_id']) + + if not volume_uuid: + # create tenant, expect ERROR_GARBAGE_ARGS if it already exists +@@ -251,9 +251,8 @@ class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,): + + def delete_share(self, context, share, share_server=None): + """Delete the corresponding Quobyte volume.""" +- volume_uuid = self._resolve_volume_name( +- share['name'], +- self._get_project_name(context, share['project_id'])) ++ volume_uuid = self._resolve_volume_name(share['name'], ++ share['project_id']) + if not volume_uuid: + LOG.warning("No volume found for " + "share %(project_id)s/%(name)s", +@@ -281,9 +280,8 @@ class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,): + the backend + """ + +- volume_uuid = self._resolve_volume_name( +- share['name'], +- self._get_project_name(context, share['project_id'])) ++ volume_uuid = self._resolve_volume_name(share['name'], ++ share['project_id']) + + LOG.debug("Ensuring Quobyte share %s", share['name']) + +@@ -303,9 +301,8 @@ class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,): + raise exception.InvalidShareAccess( + _('Quobyte driver only supports ip access control')) + +- volume_uuid = self._resolve_volume_name( +- share['name'], +- self._get_project_name(context, share['project_id'])) ++ volume_uuid = self._resolve_volume_name(share['name'], ++ share['project_id']) + ro = access['access_level'] == (constants.ACCESS_LEVEL_RO) + call_params = { + "volume_uuid": volume_uuid, +@@ -322,9 +319,8 @@ class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,): + self._get_project_name(context, share['project_id'])) + return + +- volume_uuid = self._resolve_volume_name( +- share['name'], +- self._get_project_name(context, share['project_id'])) ++ volume_uuid = self._resolve_volume_name(share['name'], ++ share['project_id']) + call_params = { + "volume_uuid": volume_uuid, + "remove_allow_ip": access['access_to']} \ No newline at end of file diff --git a/full_quobyte_patch/Pike/full_quobyte_pike_manila_driver.jsonrpc.py b/full_quobyte_patch/Pike/full_quobyte_pike_manila_driver.jsonrpc.py new file mode 100644 index 0000000..df75280 --- /dev/null +++ b/full_quobyte_patch/Pike/full_quobyte_pike_manila_driver.jsonrpc.py @@ -0,0 +1,122 @@ +# Copyright (c) 2015 Quobyte Inc. +# All Rights Reserved. +# +# 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. + +"""Quobyte driver helper. + +Control Quobyte over its JSON RPC API. +""" + +import requests +from requests import auth +from requests import codes + +from oslo_log import log +from oslo_serialization import jsonutils +import six +import six.moves.urllib.parse as urlparse + +from manila import exception +from manila import utils + +LOG = log.getLogger(__name__) + +ERROR_ENOENT = 2 +ERROR_ENTITY_NOT_FOUND = -24 +ERROR_GARBAGE_ARGS = -3 + + +class JsonRpc(object): + + def __init__(self, url, user_credentials, ca_file=None, key_file=None, + cert_file=None): + parsedurl = urlparse.urlparse(url) + self._url = parsedurl.geturl() + self._netloc = parsedurl.netloc + self._ca_file = ca_file + self._url_scheme = parsedurl.scheme + if self._url_scheme == 'https': + if not self._ca_file: + self._ca_file = False + LOG.warning( + "Will not verify the server certificate of the API service" + " because the CA certificate is not available.") + self._id = 0 + self._credentials = auth.HTTPBasicAuth( + user_credentials[0], user_credentials[1]) + self._key_file = key_file + self._cert_file = cert_file + + @utils.synchronized('quobyte-request') + def call(self, method_name, user_parameters, expected_errors=None): + if expected_errors is None: + expected_errors = [] + # prepare request + self._id += 1 + parameters = {'retry': 'INFINITELY'} # Backend specific setting + if user_parameters: + parameters.update(user_parameters) + post_data = { + 'jsonrpc': '2.0', + 'method': method_name, + 'params': parameters, + 'id': six.text_type(self._id), + } + LOG.debug("Request payload to be send is: %s", + jsonutils.dumps(post_data)) + + # send request + if self._url_scheme == 'https': + if self._cert_file: + result = requests.post(url=self._url, + json=post_data, + auth=self._credentials, + verify=self._ca_file, + cert=(self._cert_file, self._key_file)) + else: + result = requests.post(url=self._url, + json=post_data, + auth=self._credentials, + verify=self._ca_file) + else: + result = requests.post(url=self._url, + json=post_data, + auth=self._credentials) + + # eval request response + if result.status_code == codes['OK']: + LOG.debug("Retrieved data from Quobyte backend: %s", result.text) + response = result.json() + return self._checked_for_application_error(response, + expected_errors) + + # If things did not work out provide error info + LOG.debug("Backend request resulted in error: %s", result.text) + result.raise_for_status() + + def _checked_for_application_error(self, result, expected_errors=None): + if expected_errors is None: + expected_errors = [] + if 'error' in result and result['error']: + if 'message' in result['error'] and 'code' in result['error']: + if result["error"]["code"] in expected_errors: + # hit an expected error, return empty result + return None + else: + raise exception.QBRpcException( + result=result["error"]["message"], + qbcode=result["error"]["code"]) + else: + raise exception.QBException(six.text_type(result["error"])) + return result["result"] diff --git a/full_quobyte_patch/Pike/full_quobyte_pike_manila_driver.quobyte.py b/full_quobyte_patch/Pike/full_quobyte_pike_manila_driver.quobyte.py new file mode 100644 index 0000000..1b21ac2 --- /dev/null +++ b/full_quobyte_patch/Pike/full_quobyte_pike_manila_driver.quobyte.py @@ -0,0 +1,401 @@ +# Copyright (c) 2015 Quobyte Inc. +# All Rights Reserved. +# +# 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. + +""" +Quobyte driver. + +Manila shares are directly mapped to Quobyte volumes. The access to the +shares is provided by the Quobyte NFS proxy (a Ganesha NFS server). +""" + +import math + +from oslo_config import cfg +from oslo_log import log +from oslo_utils import units +import six + +from manila.common import constants +from manila import exception +from manila.i18n import _ +from manila.share import driver +from manila.share.drivers.quobyte import jsonrpc + +LOG = log.getLogger(__name__) + +quobyte_manila_share_opts = [ + cfg.StrOpt('quobyte_api_url', + help='URL of the Quobyte API server (http or https)'), + cfg.StrOpt('quobyte_api_ca', + help='The X.509 CA file to verify the server cert.'), + cfg.BoolOpt('quobyte_delete_shares', + default=False, + help='Actually deletes shares (vs. unexport)'), + cfg.StrOpt('quobyte_api_username', + default='admin', + help='Username for Quobyte API server.'), + cfg.StrOpt('quobyte_api_password', + default='quobyte', + secret=True, + help='Password for Quobyte API server'), + cfg.StrOpt('quobyte_volume_configuration', + default='BASE', + help='Name of volume configuration used for new shares.'), + cfg.StrOpt('quobyte_default_volume_user', + default='root', + help='Default owning user for new volumes.'), + cfg.StrOpt('quobyte_default_volume_group', + default='root', + help='Default owning group for new volumes.'), +] + +CONF = cfg.CONF +CONF.register_opts(quobyte_manila_share_opts) + + +class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,): + """Map share commands to Quobyte volumes. + + Version history: + 1.0 - Initial driver. + 1.0.1 - Adds ensure_share() implementation. + 1.1 - Adds extend_share() and shrink_share() implementation. + 1.2 - Adds update_access() implementation and related methods + 1.2.1 - Improved capacity calculation + 1.2.2 - Minor optimizations + 1.2.3 - Updated RPC layer for improved stability + 1.2.4 - Fixed handling updated QB API error codes + 1.2.5 - Fixed two quota handling bugs + 1.2.6 - Fixed volume resize and jsonrpc code style bugs + """ + + DRIVER_VERSION = '1.2.6' + + def __init__(self, *args, **kwargs): + super(QuobyteShareDriver, self).__init__(False, *args, **kwargs) + self.configuration.append_config_values(quobyte_manila_share_opts) + self.backend_name = (self.configuration.safe_get('share_backend_name') + or CONF.share_backend_name or 'Quobyte') + + def _fetch_existing_access(self, context, share): + volume_uuid = self._resolve_volume_name(share['name'], + share['project_id']) + result = self.rpc.call('getConfiguration', {}) + if result is None: + raise exception.QBException( + "Could not retrieve Quobyte configuration data!") + tenant_configs = result['tenant_configuration'] + qb_access_list = [] + for tc in tenant_configs: + for va in tc['volume_access']: + if va['volume_uuid'] == volume_uuid: + a_level = constants.ACCESS_LEVEL_RW + if va['read_only']: + a_level = constants.ACCESS_LEVEL_RO + qb_access_list.append({ + 'access_to': va['restrict_to_network'], + 'access_level': a_level, + 'access_type': 'ip' + }) + return qb_access_list + + def do_setup(self, context): + """Prepares the backend.""" + self.rpc = jsonrpc.JsonRpc( + url=self.configuration.quobyte_api_url, + ca_file=self.configuration.quobyte_api_ca, + user_credentials=( + self.configuration.quobyte_api_username, + self.configuration.quobyte_api_password)) + + try: + self.rpc.call('getInformation', {}) + except Exception as exc: + LOG.error("Could not connect to API: %s", exc) + raise exception.QBException( + _('Could not connect to API: %s') % exc) + + def _update_share_stats(self): + total_gb, free_gb = self._get_capacities() + + data = dict( + storage_protocol='NFS', + vendor_name='Quobyte', + share_backend_name=self.backend_name, + driver_version=self.DRIVER_VERSION, + total_capacity_gb=total_gb, + free_capacity_gb=free_gb, + reserved_percentage=self.configuration.reserved_share_percentage) + super(QuobyteShareDriver, self)._update_share_stats(data) + + def _get_capacities(self): + result = self.rpc.call('getSystemStatistics', {}) + + total = float(result['total_physical_capacity']) + used = float(result['total_physical_usage']) + LOG.info('Read capacity of %(cap)s bytes and ' + 'usage of %(use)s bytes from backend. ', + {'cap': total, 'use': used}) + free = total - used + if free < 0: + free = 0 # no space available + free_replicated = free / self._get_qb_replication_factor() + # floor numbers to nine digits (bytes) + total = math.floor((total / units.Gi) * units.G) / units.G + free = math.floor((free_replicated / units.Gi) * units.G) / units.G + + return total, free + + def _get_qb_replication_factor(self): + result = self.rpc.call('getEffectiveVolumeConfiguration', + {'configuration_name': self. + configuration.quobyte_volume_configuration}) + return int(result['configuration']['volume_metadata_configuration'] + ['replication_factor']) + + def check_for_setup_error(self): + pass + + def get_network_allocations_number(self): + return 0 + + def _get_project_name(self, context, project_id): + """Retrieve the project name. + + TODO (kaisers): retrieve the project name in order + to store and use in the backend for better usability. + """ + return project_id + + def _resize_share(self, share, new_size): + newsize_bytes = new_size * units.Gi + self.rpc.call('setQuota', {"quotas": [ + {"consumer": + [{"type": "VOLUME", + "identifier": self._resolve_volume_name(share["name"], + share['project_id']), + "tenant_id": share["project_id"]}], + "limits": [{"type": "LOGICAL_DISK_SPACE", + "value": newsize_bytes}]} + ]}) + + def _resolve_volume_name(self, volume_name, tenant_domain): + """Resolve a volume name to the global volume uuid.""" + result = self.rpc.call('resolveVolumeName', dict( + volume_name=volume_name, + tenant_domain=tenant_domain), [jsonrpc.ERROR_ENOENT, + jsonrpc.ERROR_ENTITY_NOT_FOUND]) + if result: + return result['volume_uuid'] + return None # not found + + def _subtract_access_lists(self, list_a, list_b): + """Returns a list of elements in list_a that are not in list_b + + :param list_a: Base list of access rules + :param list_b: List of access rules not to be returned + :return: List of elements of list_a not present in + list_b + """ + sub_tuples_list = [{"to": s.get('access_to'), + "type": s.get('access_type'), + "level": s.get('access_level')} + for s in list_b] + return [r for r in list_a if ( + {"to": r.get("access_to"), + "type": r.get("access_type"), + "level": r.get("access_level")} not in sub_tuples_list)] + + def create_share(self, context, share, share_server=None): + """Create or export a volume that is usable as a Manila share.""" + if share['share_proto'] != 'NFS': + raise exception.QBException( + _('Quobyte driver only supports NFS shares')) + + volume_uuid = self._resolve_volume_name(share['name'], + share['project_id']) + + if not volume_uuid: + # create tenant, expect ERROR_GARBAGE_ARGS if it already exists + self.rpc.call('setTenant', + dict(tenant=dict(tenant_id=share['project_id'])), + expected_errors=[jsonrpc.ERROR_GARBAGE_ARGS]) + result = self.rpc.call('createVolume', dict( + name=share['name'], + tenant_domain=share['project_id'], + root_user_id=self.configuration.quobyte_default_volume_user, + root_group_id=self.configuration.quobyte_default_volume_group, + configuration_name=(self.configuration. + quobyte_volume_configuration))) + volume_uuid = result['volume_uuid'] + + result = self.rpc.call('exportVolume', dict( + volume_uuid=volume_uuid, + protocol='NFS')) + + self._resize_share(share, share['size']) + + return '%(nfs_server_ip)s:%(nfs_export_path)s' % result + + def delete_share(self, context, share, share_server=None): + """Delete the corresponding Quobyte volume.""" + volume_uuid = self._resolve_volume_name(share['name'], + share['project_id']) + if not volume_uuid: + LOG.warning("No volume found for " + "share %(project_id)s/%(name)s", + {"project_id": share['project_id'], + "name": share['name']}) + return + + if self.configuration.quobyte_delete_shares: + self.rpc.call('deleteVolume', {'volume_uuid': volume_uuid}) + else: + self.rpc.call('exportVolume', {"volume_uuid": volume_uuid, + "remove_export": True, + }) + + def ensure_share(self, context, share, share_server=None): + """Invoked to ensure that share is exported. + + :param context: The `context.RequestContext` object for the request + :param share: Share instance that will be checked. + :param share_server: Data structure with share server information. + Not used by this driver. + :returns: IP: of share + :raises: + :ShareResourceNotFound: If the share instance cannot be found in + the backend + """ + + volume_uuid = self._resolve_volume_name(share['name'], + share['project_id']) + + LOG.debug("Ensuring Quobyte share %s", share['name']) + + if not volume_uuid: + raise (exception.ShareResourceNotFound( + share_id=share['id'])) + + result = self.rpc.call('exportVolume', dict( + volume_uuid=volume_uuid, + protocol='NFS')) + + return '%(nfs_server_ip)s:%(nfs_export_path)s' % result + + def _allow_access(self, context, share, access, share_server=None): + """Allow access to a share.""" + if access['access_type'] != 'ip': + raise exception.InvalidShareAccess( + _('Quobyte driver only supports ip access control')) + + volume_uuid = self._resolve_volume_name(share['name'], + share['project_id']) + ro = access['access_level'] == (constants.ACCESS_LEVEL_RO) + call_params = { + "volume_uuid": volume_uuid, + "read_only": ro, + "add_allow_ip": access['access_to']} + self.rpc.call('exportVolume', call_params) + + def _deny_access(self, context, share, access, share_server=None): + """Remove white-list ip from a share.""" + if access['access_type'] != 'ip': + LOG.debug('Quobyte driver only supports ip access control. ' + 'Ignoring deny access call for %s , %s', + share['name'], + self._get_project_name(context, share['project_id'])) + return + + volume_uuid = self._resolve_volume_name(share['name'], + share['project_id']) + call_params = { + "volume_uuid": volume_uuid, + "remove_allow_ip": access['access_to']} + self.rpc.call('exportVolume', call_params) + + def extend_share(self, ext_share, ext_size, share_server=None): + """Uses _resize_share to extend a share. + + :param ext_share: Share model. + :param ext_size: New size of share (new_size > share['size']). + :param share_server: Currently not used. + """ + self._resize_share(share=ext_share, new_size=ext_size) + + def shrink_share(self, shrink_share, shrink_size, share_server=None): + """Uses _resize_share to shrink a share. + + Quobyte uses soft quotas. If a shares current size is bigger than + the new shrunken size no data is lost. Data can be continuously read + from the share but new writes receive out of disk space replies. + + :param shrink_share: Share model. + :param shrink_size: New size of share (new_size < share['size']). + :param share_server: Currently not used. + """ + self._resize_share(share=shrink_share, new_size=shrink_size) + + def update_access(self, context, share, access_rules, add_rules, + delete_rules, share_server=None): + """Update access rules for given share. + + Two different cases are supported in here: + 1. Recovery after error - 'access_rules' contains all access_rules, + 'add_rules' and 'delete_rules' are empty. Driver should apply all + access rules for given share. + + 2. Adding/Deleting of several access rules - 'access_rules' contains + all access_rules, 'add_rules' and 'delete_rules' contain rules which + should be added/deleted. Driver can ignore rules in 'access_rules' and + apply only rules from 'add_rules' and 'delete_rules'. + + :param context: Current context + :param share: Share model with share data. + :param access_rules: All access rules for given share + :param add_rules: Empty List or List of access rules which should be + added. access_rules already contains these rules. + :param delete_rules: Empty List or List of access rules which should be + removed. access_rules doesn't contain these rules. + :param share_server: None or Share server model + :raises If all of the *_rules params are None the method raises an + InvalidShareAccess exception + """ + if (add_rules or delete_rules): + # Handling access rule update + for d_rule in delete_rules: + self._deny_access(context, share, d_rule) + for a_rule in add_rules: + self._allow_access(context, share, a_rule) + else: + if not access_rules: + LOG.warning("No access rules provided in update_access.") + else: + # Handling access rule recovery + existing_rules = self._fetch_existing_access(context, share) + + missing_rules = self._subtract_access_lists(access_rules, + existing_rules) + for a_rule in missing_rules: + LOG.debug("Adding rule %s in recovery.", + six.text_type(a_rule)) + self._allow_access(context, share, a_rule) + + superfluous_rules = self._subtract_access_lists(existing_rules, + access_rules) + for d_rule in superfluous_rules: + LOG.debug("Removing rule %s in recovery.", + six.text_type(d_rule)) + self._deny_access(context, share, d_rule) diff --git a/full_quobyte_patch/Pike/full_quobyte_pike_manila_full_source.patch b/full_quobyte_patch/Pike/full_quobyte_pike_manila_full_source.patch new file mode 100644 index 0000000..8155b2e --- /dev/null +++ b/full_quobyte_patch/Pike/full_quobyte_pike_manila_full_source.patch @@ -0,0 +1,168 @@ +diff --git a/manila/share/drivers/quobyte/jsonrpc.py b/manila/share/drivers/quobyte/jsonrpc.py +index ba4b60ec..df752802 100644 +--- a/manila/share/drivers/quobyte/jsonrpc.py ++++ b/manila/share/drivers/quobyte/jsonrpc.py +@@ -59,7 +59,9 @@ class JsonRpc(object): + self._cert_file = cert_file + + @utils.synchronized('quobyte-request') +- def call(self, method_name, user_parameters, expected_errors=[]): ++ def call(self, method_name, user_parameters, expected_errors=None): ++ if expected_errors is None: ++ expected_errors = [] + # prepare request + self._id += 1 + parameters = {'retry': 'INFINITELY'} # Backend specific setting +@@ -103,7 +105,9 @@ class JsonRpc(object): + LOG.debug("Backend request resulted in error: %s", result.text) + result.raise_for_status() + +- def _checked_for_application_error(self, result, expected_errors=[]): ++ def _checked_for_application_error(self, result, expected_errors=None): ++ if expected_errors is None: ++ expected_errors = [] + if 'error' in result and result['error']: + if 'message' in result['error'] and 'code' in result['error']: + if result["error"]["code"] in expected_errors: +diff --git a/manila/share/drivers/quobyte/quobyte.py b/manila/share/drivers/quobyte/quobyte.py +index 55d7eeb9..1b21ac24 100644 +--- a/manila/share/drivers/quobyte/quobyte.py ++++ b/manila/share/drivers/quobyte/quobyte.py +@@ -78,9 +78,10 @@ class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,): + 1.2.3 - Updated RPC layer for improved stability + 1.2.4 - Fixed handling updated QB API error codes + 1.2.5 - Fixed two quota handling bugs ++ 1.2.6 - Fixed volume resize and jsonrpc code style bugs + """ + +- DRIVER_VERSION = '1.2.5' ++ DRIVER_VERSION = '1.2.6' + + def __init__(self, *args, **kwargs): + super(QuobyteShareDriver, self).__init__(False, *args, **kwargs) +@@ -89,9 +90,8 @@ class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,): + or CONF.share_backend_name or 'Quobyte') + + def _fetch_existing_access(self, context, share): +- volume_uuid = self._resolve_volume_name( +- share['name'], +- self._get_project_name(context, share['project_id'])) ++ volume_uuid = self._resolve_volume_name(share['name'], ++ share['project_id']) + result = self.rpc.call('getConfiguration', {}) + if result is None: + raise exception.QBException( +@@ -184,7 +184,8 @@ class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,): + self.rpc.call('setQuota', {"quotas": [ + {"consumer": + [{"type": "VOLUME", +- "identifier": share["name"], ++ "identifier": self._resolve_volume_name(share["name"], ++ share['project_id']), + "tenant_id": share["project_id"]}], + "limits": [{"type": "LOGICAL_DISK_SPACE", + "value": newsize_bytes}]} +@@ -223,9 +224,8 @@ class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,): + raise exception.QBException( + _('Quobyte driver only supports NFS shares')) + +- volume_uuid = self._resolve_volume_name( +- share['name'], +- self._get_project_name(context, share['project_id'])) ++ volume_uuid = self._resolve_volume_name(share['name'], ++ share['project_id']) + + if not volume_uuid: + # create tenant, expect ERROR_GARBAGE_ARGS if it already exists +@@ -251,9 +251,8 @@ class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,): + + def delete_share(self, context, share, share_server=None): + """Delete the corresponding Quobyte volume.""" +- volume_uuid = self._resolve_volume_name( +- share['name'], +- self._get_project_name(context, share['project_id'])) ++ volume_uuid = self._resolve_volume_name(share['name'], ++ share['project_id']) + if not volume_uuid: + LOG.warning("No volume found for " + "share %(project_id)s/%(name)s", +@@ -281,9 +280,8 @@ class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,): + the backend + """ + +- volume_uuid = self._resolve_volume_name( +- share['name'], +- self._get_project_name(context, share['project_id'])) ++ volume_uuid = self._resolve_volume_name(share['name'], ++ share['project_id']) + + LOG.debug("Ensuring Quobyte share %s", share['name']) + +@@ -303,9 +301,8 @@ class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,): + raise exception.InvalidShareAccess( + _('Quobyte driver only supports ip access control')) + +- volume_uuid = self._resolve_volume_name( +- share['name'], +- self._get_project_name(context, share['project_id'])) ++ volume_uuid = self._resolve_volume_name(share['name'], ++ share['project_id']) + ro = access['access_level'] == (constants.ACCESS_LEVEL_RO) + call_params = { + "volume_uuid": volume_uuid, +@@ -322,9 +319,8 @@ class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,): + self._get_project_name(context, share['project_id'])) + return + +- volume_uuid = self._resolve_volume_name( +- share['name'], +- self._get_project_name(context, share['project_id'])) ++ volume_uuid = self._resolve_volume_name(share['name'], ++ share['project_id']) + call_params = { + "volume_uuid": volume_uuid, + "remove_allow_ip": access['access_to']} +diff --git a/manila/tests/share/drivers/quobyte/test_quobyte.py b/manila/tests/share/drivers/quobyte/test_quobyte.py +index a43b3c01..1da79daa 100644 +--- a/manila/tests/share/drivers/quobyte/test_quobyte.py ++++ b/manila/tests/share/drivers/quobyte/test_quobyte.py +@@ -416,7 +416,9 @@ class QuobyteShareDriverTestCase(test.TestCase): + mock_qsd_resize_share.assert_called_once_with(share=self.share, + new_size=2) + +- def test_resize_share(self): ++ @mock.patch.object(quobyte.QuobyteShareDriver, "_resolve_volume_name", ++ return_value="fake_volume_uuid") ++ def test_resize_share(self, mock_qb_resolv): + self._driver.rpc.call = mock.Mock(wraps=fake_rpc_handler) + manila_size = 7 + newsize_bytes = manila_size * units.Gi +@@ -427,7 +429,7 @@ class QuobyteShareDriverTestCase(test.TestCase): + "quotas": [{ + "consumer": [{ + "type": "VOLUME", +- "identifier": self.share["name"], ++ "identifier": "fake_volume_uuid", + "tenant_id": self.share["project_id"] + }], + "limits": [{ +@@ -437,6 +439,8 @@ class QuobyteShareDriverTestCase(test.TestCase): + }]} + self._driver.rpc.call.assert_has_calls([ + mock.call('setQuota', exp_params)]) ++ mock_qb_resolv.assert_called_once_with(self.share['name'], ++ self.share['project_id']) + + @mock.patch.object(quobyte.QuobyteShareDriver, + "_resolve_volume_name", +diff --git a/releasenotes/notes/bug-1774604-qb-driver-b7e717cbc71d6189.yaml b/releasenotes/notes/bug-1774604-qb-driver-b7e717cbc71d6189.yaml +new file mode 100644 +index 00000000..f3de72a6 +--- /dev/null ++++ b/releasenotes/notes/bug-1774604-qb-driver-b7e717cbc71d6189.yaml +@@ -0,0 +1,5 @@ ++--- ++fixes: ++ - | ++ Fixed a bug in the Quobyte driver that allowed share resizing to ++ incorrectly address the share to be resized in the backend. diff --git a/full_quobyte_patch/Pike/full_quobyte_pike_nova.patch b/full_quobyte_patch/Pike/full_quobyte_pike_nova.patch new file mode 100644 index 0000000..c2b370c --- /dev/null +++ b/full_quobyte_patch/Pike/full_quobyte_pike_nova.patch @@ -0,0 +1,34 @@ +diff --git a/nova/virt/libvirt/volume/quobyte.py b/nova/virt/libvirt/volume/quobyte.py +index 462beab3e5..c9b6932a47 100644 +--- a/nova/virt/libvirt/volume/quobyte.py ++++ b/nova/virt/libvirt/volume/quobyte.py +@@ -47,10 +47,10 @@ def mount_volume(volume, mnt_base, configfile=None): + # additional metadata requests in the backend. xattrs can be + # enabled without issues but will reduce performance. + command = ['mount.quobyte', '--disable-xattrs', volume, mnt_base] +- if os.path.exists(" /run/systemd/system"): ++ if os.path.exists("/run/systemd/system"): + # Note(kaisers): with systemd this requires a separate CGROUP to + # prevent Nova service stop/restarts from killing the mount. +- command = ['systemd-run', '--scope', '--user', 'mount.quobyte', ++ command = ['systemd-run', '--scope', 'mount.quobyte', + '--disable-xattrs', volume, mnt_base] + if configfile: + command.extend(['-c', configfile]) +@@ -58,14 +58,14 @@ def mount_volume(volume, mnt_base, configfile=None): + LOG.debug('Mounting volume %s at mount point %s ...', + volume, + mnt_base) +- utils.execute(*command) ++ utils.execute(*command, run_as_root=True) + LOG.info('Mounted volume: %s', volume) + + + def umount_volume(mnt_base): + """Wraps execute calls for unmouting a Quobyte volume""" + try: +- utils.execute('umount.quobyte', mnt_base) ++ utils.execute('umount.quobyte', mnt_base, run_as_root=True) + except processutils.ProcessExecutionError as exc: + if 'Device or resource busy' in six.text_type(exc): + LOG.error("The Quobyte volume at %s is still in use.", mnt_base) diff --git a/full_quobyte_patch/Pike/full_quobyte_pike_nova_driver.quobyte.py b/full_quobyte_patch/Pike/full_quobyte_pike_nova_driver.quobyte.py new file mode 100644 index 0000000..c9b6932 --- /dev/null +++ b/full_quobyte_patch/Pike/full_quobyte_pike_nova_driver.quobyte.py @@ -0,0 +1,169 @@ +# Copyright (c) 2015 Quobyte Inc. +# All Rights Reserved. +# +# 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. + +import errno +import os + +from oslo_concurrency import processutils +from oslo_log import log as logging +from oslo_utils import fileutils +import psutil +import six + +import nova.conf +from nova import exception as nova_exception +from nova.i18n import _ +from nova import utils +from nova.virt.libvirt import utils as libvirt_utils +from nova.virt.libvirt.volume import fs + +LOG = logging.getLogger(__name__) + +CONF = nova.conf.CONF + +SOURCE_PROTOCOL = 'quobyte' +SOURCE_TYPE = 'file' +DRIVER_CACHE = 'none' +DRIVER_IO = 'native' + + +def mount_volume(volume, mnt_base, configfile=None): + """Wraps execute calls for mounting a Quobyte volume""" + fileutils.ensure_tree(mnt_base) + + # NOTE(kaisers): disable xattrs to speed up io as this omits + # additional metadata requests in the backend. xattrs can be + # enabled without issues but will reduce performance. + command = ['mount.quobyte', '--disable-xattrs', volume, mnt_base] + if os.path.exists("/run/systemd/system"): + # Note(kaisers): with systemd this requires a separate CGROUP to + # prevent Nova service stop/restarts from killing the mount. + command = ['systemd-run', '--scope', 'mount.quobyte', + '--disable-xattrs', volume, mnt_base] + if configfile: + command.extend(['-c', configfile]) + + LOG.debug('Mounting volume %s at mount point %s ...', + volume, + mnt_base) + utils.execute(*command, run_as_root=True) + LOG.info('Mounted volume: %s', volume) + + +def umount_volume(mnt_base): + """Wraps execute calls for unmouting a Quobyte volume""" + try: + utils.execute('umount.quobyte', mnt_base, run_as_root=True) + except processutils.ProcessExecutionError as exc: + if 'Device or resource busy' in six.text_type(exc): + LOG.error("The Quobyte volume at %s is still in use.", mnt_base) + else: + LOG.exception(_("Couldn't unmount the Quobyte Volume at %s"), + mnt_base) + + +def validate_volume(mount_path): + """Runs a number of tests to be sure this is a (working) Quobyte mount""" + partitions = psutil.disk_partitions(all=True) + for p in partitions: + if mount_path != p.mountpoint: + continue + if p.device.startswith("quobyte@"): + statresult = os.stat(mount_path) + # Note(kaisers): Quobyte always shows mount points with size 0 + if statresult.st_size == 0: + # client looks healthy + return # we're happy here + else: + msg = (_("The mount %(mount_path)s is not a " + "valid Quobyte volume. Stale mount?") + % {'mount_path': mount_path}) + raise nova_exception.InvalidVolume(msg) + else: + msg = (_("The mount %(mount_path)s is not a valid" + " Quobyte volume according to partition list.") + % {'mount_path': mount_path}) + raise nova_exception.InvalidVolume(msg) + msg = (_("No matching Quobyte mount entry for %(mount_path)s" + " could be found for validation in partition list.") + % {'mount_path': mount_path}) + raise nova_exception.InvalidVolume(msg) + + +class LibvirtQuobyteVolumeDriver(fs.LibvirtBaseFileSystemVolumeDriver): + """Class implements libvirt part of volume driver for Quobyte.""" + + def _get_mount_point_base(self): + return CONF.libvirt.quobyte_mount_point_base + + def get_config(self, connection_info, disk_info): + conf = super(LibvirtQuobyteVolumeDriver, + self).get_config(connection_info, disk_info) + data = connection_info['data'] + conf.source_protocol = SOURCE_PROTOCOL + conf.source_type = SOURCE_TYPE + conf.driver_cache = DRIVER_CACHE + conf.driver_io = DRIVER_IO + conf.driver_format = data.get('format', 'raw') + + conf.source_path = self._get_device_path(connection_info) + + return conf + + @utils.synchronized('connect_qb_volume') + def connect_volume(self, connection_info, disk_info, instance): + """Connect the volume.""" + data = connection_info['data'] + quobyte_volume = self._normalize_export(data['export']) + mount_path = self._get_mount_path(connection_info) + mounted = libvirt_utils.is_mounted(mount_path, + SOURCE_PROTOCOL + + '@' + quobyte_volume) + if mounted: + try: + os.stat(mount_path) + except OSError as exc: + if exc.errno == errno.ENOTCONN: + mounted = False + LOG.info('Fixing previous mount %s which was not' + ' unmounted correctly.', mount_path) + umount_volume(mount_path) + + if not mounted: + mount_volume(quobyte_volume, + mount_path, + CONF.libvirt.quobyte_client_cfg) + + validate_volume(mount_path) + + @utils.synchronized('connect_qb_volume') + def disconnect_volume(self, connection_info, disk_dev, instance): + """Disconnect the volume.""" + + quobyte_volume = self._normalize_export( + connection_info['data']['export']) + mount_path = self._get_mount_path(connection_info) + + if libvirt_utils.is_mounted(mount_path, 'quobyte@' + quobyte_volume): + umount_volume(mount_path) + else: + LOG.info("Trying to disconnected unmounted volume at %s", + mount_path) + + def _normalize_export(self, export): + protocol = SOURCE_PROTOCOL + "://" + if export.startswith(protocol): + export = export[len(protocol):] + return export diff --git a/full_quobyte_patch/Pike/full_quobyte_pike_nova_full_source.patch b/full_quobyte_patch/Pike/full_quobyte_pike_nova_full_source.patch new file mode 100644 index 0000000..d5a793c --- /dev/null +++ b/full_quobyte_patch/Pike/full_quobyte_pike_nova_full_source.patch @@ -0,0 +1,94 @@ +diff --git a/nova/tests/unit/virt/libvirt/volume/test_quobyte.py b/nova/tests/unit/virt/libvirt/volume/test_quobyte.py +index 73aa16d47e..f07f393ac7 100644 +--- a/nova/tests/unit/virt/libvirt/volume/test_quobyte.py ++++ b/nova/tests/unit/virt/libvirt/volume/test_quobyte.py +@@ -76,10 +76,11 @@ class QuobyteTestCase(test.NoDBTestCase): + expected_commands = [mock.call('mount.quobyte', + '--disable-xattrs', + quobyte_volume, +- export_mnt_base) ++ export_mnt_base, ++ run_as_root=True) + ] + mock_execute.assert_has_calls(expected_commands) +- mock_exists.assert_called_once_with(" /run/systemd/system") ++ mock_exists.assert_called_once_with("/run/systemd/system") + + @mock.patch.object(os.path, "exists", return_value=True) + @mock.patch.object(fileutils, "ensure_tree") +@@ -97,14 +98,14 @@ class QuobyteTestCase(test.NoDBTestCase): + mock_ensure_tree.assert_called_once_with(export_mnt_base) + expected_commands = [mock.call('systemd-run', + '--scope', +- '--user', + 'mount.quobyte', + '--disable-xattrs', + quobyte_volume, +- export_mnt_base) ++ export_mnt_base, ++ run_as_root=True) + ] + mock_execute.assert_has_calls(expected_commands) +- mock_exists.assert_called_once_with(" /run/systemd/system") ++ mock_exists.assert_called_once_with("/run/systemd/system") + + @mock.patch.object(os.path, "exists", return_value=False) + @mock.patch.object(fileutils, "ensure_tree") +@@ -129,10 +130,11 @@ class QuobyteTestCase(test.NoDBTestCase): + quobyte_volume, + export_mnt_base, + '-c', +- config_file_dummy) ++ config_file_dummy, ++ run_as_root=True) + ] + mock_execute.assert_has_calls(expected_commands) +- mock_exists.assert_called_once_with(" /run/systemd/system") ++ mock_exists.assert_called_once_with("/run/systemd/system") + + @mock.patch.object(fileutils, "ensure_tree") + @mock.patch.object(utils, "execute", +@@ -159,7 +161,8 @@ class QuobyteTestCase(test.NoDBTestCase): + quobyte.umount_volume(export_mnt_base) + + mock_execute.assert_called_once_with('umount.quobyte', +- export_mnt_base) ++ export_mnt_base, ++ run_as_root=True) + + @mock.patch.object(quobyte.LOG, "error") + @mock.patch.object(utils, "execute") +diff --git a/nova/virt/libvirt/volume/quobyte.py b/nova/virt/libvirt/volume/quobyte.py +index 462beab3e5..c9b6932a47 100644 +--- a/nova/virt/libvirt/volume/quobyte.py ++++ b/nova/virt/libvirt/volume/quobyte.py +@@ -47,10 +47,10 @@ def mount_volume(volume, mnt_base, configfile=None): + # additional metadata requests in the backend. xattrs can be + # enabled without issues but will reduce performance. + command = ['mount.quobyte', '--disable-xattrs', volume, mnt_base] +- if os.path.exists(" /run/systemd/system"): ++ if os.path.exists("/run/systemd/system"): + # Note(kaisers): with systemd this requires a separate CGROUP to + # prevent Nova service stop/restarts from killing the mount. +- command = ['systemd-run', '--scope', '--user', 'mount.quobyte', ++ command = ['systemd-run', '--scope', 'mount.quobyte', + '--disable-xattrs', volume, mnt_base] + if configfile: + command.extend(['-c', configfile]) +@@ -58,14 +58,14 @@ def mount_volume(volume, mnt_base, configfile=None): + LOG.debug('Mounting volume %s at mount point %s ...', + volume, + mnt_base) +- utils.execute(*command) ++ utils.execute(*command, run_as_root=True) + LOG.info('Mounted volume: %s', volume) + + + def umount_volume(mnt_base): + """Wraps execute calls for unmouting a Quobyte volume""" + try: +- utils.execute('umount.quobyte', mnt_base) ++ utils.execute('umount.quobyte', mnt_base, run_as_root=True) + except processutils.ProcessExecutionError as exc: + if 'Device or resource busy' in six.text_type(exc): + LOG.error("The Quobyte volume at %s is still in use.", mnt_base) diff --git a/full_quobyte_patch/README.md b/full_quobyte_patch/README.md index 9e8bff1..6cba4c6 100644 --- a/full_quobyte_patch/README.md +++ b/full_quobyte_patch/README.md @@ -1,38 +1,10 @@ -## Full Quobyte patch (Work in progress) +## Full Quobyte Patches (Continuous Work in Progress) +These patches are continuosly evolving and contain all relevant fixes and changes for the given OpenStack releases and projects. By applying these patches no other patches from this repository need to be applied to an installation. The fixed & improved issues currently patched are +listed for each release. -These patches are continuosly evolving and contain all relevant fixes and changes for the given OpenStack releases and projects. By applying these patches no other patches from this repository need to be applied to an installation. The fixed & improved issues currently patched are: - -- Ocata Release - - Cinder - - Fixes bug [1685277](https://bugs.launchpad.net/cinder/+bug/1685277) with patch [458885](https://review.openstack.org/#/c/458885/) in order to enable the usage of the Cinder Generic Volume Cache with the Quobyte driver (_this fix is only applied for the Ocata release_). - - Fixes bug [1674611](https://bugs.launchpad.net/cinder/+bug/1674611) with patch [447958](https://review.openstack.org/#/c/447958/), thus allowing e.g. cloned backups of Quobyte Cinder volumes. - - Fixes bug [1675710](https://bugs.launchpad.net/cinder/+bug/1675710) with patch [449553](https://review.openstack.org/#/c/449553/) in order to allow specifying multiple Quobyte registries in the quobyte_volume_url Cinder configuration option. - - Fixes bug [1687048](https://bugs.launchpad.net/cinder/+bug/1687048) with patch [461471](https://review.openstack.org/#/c/461471/) - - Removes the requirement to support extended attributes from Cinder mounts with patch [427833](https://review.openstack.org/#/c/427833/) for improved performance - - - Manila - - Fixes bug [1733807](https://bugs.launchpad.net/manila/+bug/1733807) with patch [570741](https://review.openstack.org/#/c/570741) for improved API compatibility. - - Fixes bug [1771958](https://bugs.launchpad.net/manila/+bug/1771958) with patch [569355](https://review.openstack.org/#/c/569355/) to fix initial quota creation. - - Fixes bug [1771970](https://bugs.launchpad.net/manila/+bug/1771970) with patch [569355](https://review.openstack.org/#/c/569355/) to correct the quota when resizing a Manila share. - - Fixes bug [1774604](https://bugs.launchpad.net/manila/+bug/1774604) with patch [571693](https://review.openstack.org/#/c/571693/) to avoid incoherent volume specification in resizing. - - Fixes bug [1773929](https://bugs.launchpad.net/manila/+bug/1773929) with patch [572291](https://review.openstack.org/#/c/572291/) to fix a coding style issue. - - - Nova - - Fixes bug [1530860](https://bugs.launchpad.net/nova/+bug/1530860) with patch [432344](https://review.openstack.org/#/c/432344/) and bug [1756823](https://bugs.launchpad.net/nova/+bug/1756823) with patch [554195](https://review.openstack.org/#/c/554195/) in order to prevent systemd service restarts from unmounting Nova mounts - - Fixes bug [1679976](https://bugs.launchpad.net/nova/+bug/1679976) with patch [453537](https://review.openstack.org/#/c/453537/) disallowing a specific and potentially bad exit code during volume mounts - - Removes the requirement to support extended attributes from Nova mounts with patch [428646](https://review.openstack.org/#/c/428646/) for improved performance - -The patches come in two versions, the pure driver and the full source patch. For Cinder these are: - -1. _full_quobyte_ocata_cinder.patch_ - The pure driver patch for patching package based installations that strip tests and other development elements -2. _full_quobyte_ocata_cinder_full_source_tree.patch_ - The full source patch including updates of unit tests and release notes files - -And for Nova: - -1. _full_quobyte_ocata_nova.patch_ - The pure driver patch for patching package based installations that strip tests and other development elements -2. _full_quobyte_ocata_nova_full_source_tree.patch_ - The full source patch including updates of unit tests and release notes files +In case you are using a missing release please contact the Quobyte support and we'll provide the required patches in this repository. ### Installation @@ -57,9 +29,17 @@ And for Nova and Manila: patch -p1 < /path/to/patchfile +#### Usage for the driver files + +Alongside the patch files we also provide the fully patched driver files in newer releases. These can simply be copied into the existing driver file locations. + ### Changelog -#### 0.5 (208-06-29) +#### 0.6 (2018-09-11) + - Adds Pike release patches + - Moves release based patch information into separate Readme.md files per release + +#### 0.5 (2018-06-29) - Updates Manila patches for fixing a share resizing and a code style issue #### 0.4 (2018-05-28)