From 65a93cb8f77a6b7ceeb2494667faf991d231b84c Mon Sep 17 00:00:00 2001 From: Ricardo Marques Date: Fri, 15 Mar 2019 13:50:09 +0000 Subject: [PATCH] mgr/dashboard: Drop iSCSI gateway name parameter iSCSI gateway name will now be obtained from the ceph-iscsi `/api/sysinfo/hostname` endpoint Signed-off-by: Ricardo Marques --- doc/mgr/dashboard.rst | 14 +-- .../mgr/dashboard/services/iscsi_cli.py | 100 ++---------------- .../mgr/dashboard/services/iscsi_client.py | 20 ++-- .../mgr/dashboard/services/iscsi_config.py | 98 +++++++++++++++++ src/pybind/mgr/dashboard/tests/test_iscsi.py | 15 ++- 5 files changed, 141 insertions(+), 106 deletions(-) create mode 100644 src/pybind/mgr/dashboard/services/iscsi_config.py diff --git a/doc/mgr/dashboard.rst b/doc/mgr/dashboard.rst index b2918c90cd3d3..a557d43eecf42 100644 --- a/doc/mgr/dashboard.rst +++ b/doc/mgr/dashboard.rst @@ -318,19 +318,19 @@ The Ceph Dashboard can manage iSCSI targets using the REST API provided by the `rbd-target-api` service of the `ceph-iscsi `_ project. Please make sure that it's installed and enabled on the iSCSI gateways. -The available iSCSI gateways must be defined using the following commands:: - - $ ceph dashboard iscsi-gateway-list - $ ceph dashboard iscsi-gateway-add ://:@[:port] - $ ceph dashboard iscsi-gateway-rm - If ceph-iscsi REST API is configured in HTTPS mode and its using a self-signed certificate, then we need to configure the dashboard to avoid SSL certificate verification when accessing ceph-iscsi API. To disable API SSL verification run the following commmand:: - $ ceph dashboard iscsi-set-api-ssl-verification false + $ ceph dashboard set-iscsi-api-ssl-verification false + +The available iSCSI gateways must be defined using the following commands:: + + $ ceph dashboard iscsi-gateway-list + $ ceph dashboard iscsi-gateway-add ://:@[:port] + $ ceph dashboard iscsi-gateway-rm .. _dashboard-grafana: diff --git a/src/pybind/mgr/dashboard/services/iscsi_cli.py b/src/pybind/mgr/dashboard/services/iscsi_cli.py index c03d69f78e07c..fca1f61b3cc14 100644 --- a/src/pybind/mgr/dashboard/services/iscsi_cli.py +++ b/src/pybind/mgr/dashboard/services/iscsi_cli.py @@ -4,97 +4,12 @@ from __future__ import absolute_import import errno import json -try: - from urlparse import urlparse -except ImportError: - from urllib.parse import urlparse - from mgr_module import CLIReadCommand, CLIWriteCommand -from .orchestrator import OrchClient -from .. import mgr - - -class IscsiGatewayAlreadyExists(Exception): - def __init__(self, gateway_name): - super(IscsiGatewayAlreadyExists, self).__init__( - "iSCSI gateway '{}' already exists".format(gateway_name)) - - -class IscsiGatewayDoesNotExist(Exception): - def __init__(self, hostname): - super(IscsiGatewayDoesNotExist, self).__init__( - "iSCSI gateway '{}' does not exist".format(hostname)) - - -class InvalidServiceUrl(Exception): - def __init__(self, service_url): - super(InvalidServiceUrl, self).__init__( - "Invalid service URL '{}'. " - "Valid format: '://:@[:port]'.".format(service_url)) - - -class ManagedByOrchestratorException(Exception): - def __init__(self): - super(ManagedByOrchestratorException, self).__init__( - "iSCSI configuration is managed by the orchestrator") - - -_ISCSI_STORE_KEY = "_iscsi_config" - - -class IscsiGatewaysConfig(object): - @classmethod - def _load_config(cls): - if OrchClient.instance().available(): - raise ManagedByOrchestratorException() - json_db = mgr.get_store(_ISCSI_STORE_KEY, - '{"gateways": {}}') - return json.loads(json_db) - - @classmethod - def _save_config(cls, config): - mgr.set_store(_ISCSI_STORE_KEY, json.dumps(config)) - - @classmethod - def add_gateway(cls, name, service_url): - config = cls._load_config() - if name in config: - raise IscsiGatewayAlreadyExists(name) - url = urlparse(service_url) - if not url.scheme or not url.hostname or not url.username or not url.password: - raise InvalidServiceUrl(service_url) - config['gateways'][name] = {'service_url': service_url} - cls._save_config(config) - - @classmethod - def remove_gateway(cls, name): - config = cls._load_config() - if name not in config['gateways']: - raise IscsiGatewayDoesNotExist(name) - - del config['gateways'][name] - cls._save_config(config) - - @classmethod - def get_gateways_config(cls): - try: - config = cls._load_config() - except ManagedByOrchestratorException: - config = {'gateways': {}} - instances = OrchClient.instance().list_service_info("iscsi") - for instance in instances: - config['gateways'][instance.nodename] = { - 'service_url': instance.service_url - } - return config - - @classmethod - def get_gateway_config(cls, name): - config = IscsiGatewaysConfig.get_gateways_config() - if name not in config['gateways']: - raise IscsiGatewayDoesNotExist(name) - return config['gateways'][name] +from .iscsi_client import IscsiClient +from .iscsi_config import IscsiGatewaysConfig, IscsiGatewayAlreadyExists, InvalidServiceUrl, \ + ManagedByOrchestratorException, IscsiGatewayDoesNotExist +from ..rest_client import RequestException @CLIReadCommand('dashboard iscsi-gateway-list', desc='List iSCSI gateways') @@ -103,11 +18,12 @@ def list_iscsi_gateways(_): @CLIWriteCommand('dashboard iscsi-gateway-add', - 'name=name,type=CephString ' 'name=service_url,type=CephString', 'Add iSCSI gateway configuration') -def add_iscsi_gateway(_, name, service_url): +def add_iscsi_gateway(_, service_url): try: + IscsiGatewaysConfig.validate_service_url(service_url) + name = IscsiClient.instance(service_url=service_url).get_hostname()['data'] IscsiGatewaysConfig.add_gateway(name, service_url) return 0, 'Success', '' except IscsiGatewayAlreadyExists as ex: @@ -116,6 +32,8 @@ def add_iscsi_gateway(_, name, service_url): return -errno.EINVAL, '', str(ex) except ManagedByOrchestratorException as ex: return -errno.EINVAL, '', str(ex) + except RequestException as ex: + return -errno.EINVAL, '', str(ex) @CLIWriteCommand('dashboard iscsi-gateway-rm', diff --git a/src/pybind/mgr/dashboard/services/iscsi_client.py b/src/pybind/mgr/dashboard/services/iscsi_client.py index 92d3a16e52a0f..e69c621b5586f 100644 --- a/src/pybind/mgr/dashboard/services/iscsi_client.py +++ b/src/pybind/mgr/dashboard/services/iscsi_client.py @@ -11,7 +11,7 @@ try: except ImportError: from urllib.parse import urlparse -from .iscsi_cli import IscsiGatewaysConfig +from .iscsi_config import IscsiGatewaysConfig from .. import logger from ..settings import Settings from ..rest_client import RestClient @@ -24,11 +24,12 @@ class IscsiClient(RestClient): service_url = None @classmethod - def instance(cls, gateway_name=None): - if not gateway_name: - gateway_name = list(IscsiGatewaysConfig.get_gateways_config()['gateways'].keys())[0] - gateways_config = IscsiGatewaysConfig.get_gateway_config(gateway_name) - service_url = gateways_config['service_url'] + def instance(cls, gateway_name=None, service_url=None): + if not service_url: + if not gateway_name: + gateway_name = list(IscsiGatewaysConfig.get_gateways_config()['gateways'].keys())[0] + gateways_config = IscsiGatewaysConfig.get_gateway_config(gateway_name) + service_url = gateways_config['service_url'] instance = cls._instances.get(gateway_name) if not instance or service_url != instance.service_url or \ @@ -46,7 +47,8 @@ class IscsiClient(RestClient): instance = IscsiClient(host, port, IscsiClient._CLIENT_NAME, ssl, auth, Settings.ISCSI_API_SSL_VERIFICATION) instance.service_url = service_url - cls._instances[gateway_name] = instance + if gateway_name: + cls._instances[gateway_name] = instance return instance @@ -62,6 +64,10 @@ class IscsiClient(RestClient): def get_ip_addresses(self, request=None): return request() + @RestClient.api_get('/api/sysinfo/hostname') + def get_hostname(self, request=None): + return request() + @RestClient.api_get('/api/config') def get_config(self, request=None): return request({ diff --git a/src/pybind/mgr/dashboard/services/iscsi_config.py b/src/pybind/mgr/dashboard/services/iscsi_config.py new file mode 100644 index 0000000000000..df6537c0c54c1 --- /dev/null +++ b/src/pybind/mgr/dashboard/services/iscsi_config.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + +import json + +try: + from urlparse import urlparse +except ImportError: + from urllib.parse import urlparse + +from .orchestrator import OrchClient +from .. import mgr + + +class IscsiGatewayAlreadyExists(Exception): + def __init__(self, gateway_name): + super(IscsiGatewayAlreadyExists, self).__init__( + "iSCSI gateway '{}' already exists".format(gateway_name)) + + +class IscsiGatewayDoesNotExist(Exception): + def __init__(self, hostname): + super(IscsiGatewayDoesNotExist, self).__init__( + "iSCSI gateway '{}' does not exist".format(hostname)) + + +class InvalidServiceUrl(Exception): + def __init__(self, service_url): + super(InvalidServiceUrl, self).__init__( + "Invalid service URL '{}'. " + "Valid format: '://:@[:port]'.".format(service_url)) + + +class ManagedByOrchestratorException(Exception): + def __init__(self): + super(ManagedByOrchestratorException, self).__init__( + "iSCSI configuration is managed by the orchestrator") + + +_ISCSI_STORE_KEY = "_iscsi_config" + + +class IscsiGatewaysConfig(object): + @classmethod + def _load_config(cls): + if OrchClient.instance().available(): + raise ManagedByOrchestratorException() + json_db = mgr.get_store(_ISCSI_STORE_KEY, + '{"gateways": {}}') + return json.loads(json_db) + + @classmethod + def _save_config(cls, config): + mgr.set_store(_ISCSI_STORE_KEY, json.dumps(config)) + + @classmethod + def validate_service_url(cls, service_url): + url = urlparse(service_url) + if not url.scheme or not url.hostname or not url.username or not url.password: + raise InvalidServiceUrl(service_url) + + @classmethod + def add_gateway(cls, name, service_url): + config = cls._load_config() + if name in config: + raise IscsiGatewayAlreadyExists(name) + IscsiGatewaysConfig.validate_service_url(service_url) + config['gateways'][name] = {'service_url': service_url} + cls._save_config(config) + + @classmethod + def remove_gateway(cls, name): + config = cls._load_config() + if name not in config['gateways']: + raise IscsiGatewayDoesNotExist(name) + + del config['gateways'][name] + cls._save_config(config) + + @classmethod + def get_gateways_config(cls): + try: + config = cls._load_config() + except ManagedByOrchestratorException: + config = {'gateways': {}} + instances = OrchClient.instance().list_service_info("iscsi") + for instance in instances: + config['gateways'][instance.nodename] = { + 'service_url': instance.service_url + } + return config + + @classmethod + def get_gateway_config(cls, name): + config = IscsiGatewaysConfig.get_gateways_config() + if name not in config['gateways']: + raise IscsiGatewayDoesNotExist(name) + return config['gateways'][name] diff --git a/src/pybind/mgr/dashboard/tests/test_iscsi.py b/src/pybind/mgr/dashboard/tests/test_iscsi.py index f86ac2a9e1014..c8d99eea280ab 100644 --- a/src/pybind/mgr/dashboard/tests/test_iscsi.py +++ b/src/pybind/mgr/dashboard/tests/test_iscsi.py @@ -10,6 +10,7 @@ from .. import mgr from ..controllers.iscsi import Iscsi, IscsiTarget from ..services.iscsi_client import IscsiClient from ..services.orchestrator import OrchClient +from ..rest_client import RequestException class IscsiTest(ControllerTestCase, CLICommandTestMixin): @@ -440,6 +441,7 @@ class IscsiClientMock(object): def __init__(self): self.gateway_name = None + self.service_url = None self.config = { "created": "2019/01/17 08:57:16", "discovery_auth": { @@ -459,8 +461,9 @@ class IscsiClientMock(object): } @classmethod - def instance(cls, gateway_name=None): + def instance(cls, gateway_name=None, service_url=None): cls._instance.gateway_name = gateway_name + cls._instance.service_url = service_url # pylint: disable=unused-argument return cls._instance @@ -606,6 +609,16 @@ class IscsiClientMock(object): } return {'data': ips[self.gateway_name]} + def get_hostname(self): + hostnames = { + 'https://admin:admin@10.17.5.1:5001': 'node1', + 'https://admin:admin@10.17.5.2:5001': 'node2', + 'https://admin:admin@10.17.5.3:5001': 'node3' + } + if self.service_url not in hostnames: + raise RequestException('No route to host') + return {'data': hostnames[self.service_url]} + def update_discoveryauth(self, user, password, mutual_user, mutual_password): self.config['discovery_auth']['username'] = user self.config['discovery_auth']['password'] = password -- 2.39.5