]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/pybind/mgr/cephadm/services/iscsi.py
import quincy beta 17.1.0
[ceph.git] / ceph / src / pybind / mgr / cephadm / services / iscsi.py
index 2fce233e3696ab11d857f91c66eeeaaa26c1640e..d5896f075d497499b3b35eb2136920c2e78f13fc 100644 (file)
@@ -1,12 +1,15 @@
+import errno
 import json
 import logging
-from typing import List, cast
+import subprocess
+from typing import List, cast, Optional
+from ipaddress import ip_address, IPv6Address
 
-from mgr_module import MonCommandFailed
+from mgr_module import HandleCommandResult
 from ceph.deployment.service_spec import IscsiServiceSpec
 
-from orchestrator import DaemonDescription, OrchestratorError
-from .cephadmservice import CephadmDaemonSpec, CephService
+from orchestrator import DaemonDescription, DaemonDescriptionStatus
+from .cephadmservice import CephadmDaemonDeploySpec, CephService
 from .. import utils
 
 logger = logging.getLogger(__name__)
@@ -15,30 +18,23 @@ logger = logging.getLogger(__name__)
 class IscsiService(CephService):
     TYPE = 'iscsi'
 
-    def config(self, spec: IscsiServiceSpec) -> None:
+    def config(self, spec: IscsiServiceSpec) -> None:  # type: ignore
         assert self.TYPE == spec.service_type
         assert spec.pool
         self.mgr._check_pool_exists(spec.pool, spec.service_name())
 
-        logger.info('Saving service %s spec with placement %s' % (
-            spec.service_name(), spec.placement.pretty_str()))
-        self.mgr.spec_store.save(spec)
-
-    def prepare_create(self, daemon_spec: CephadmDaemonSpec[IscsiServiceSpec]) -> CephadmDaemonSpec:
+    def prepare_create(self, daemon_spec: CephadmDaemonDeploySpec) -> CephadmDaemonDeploySpec:
         assert self.TYPE == daemon_spec.daemon_type
-        assert daemon_spec.spec
 
-        spec = daemon_spec.spec
+        spec = cast(IscsiServiceSpec, self.mgr.spec_store[daemon_spec.service_name].spec)
         igw_id = daemon_spec.daemon_id
 
-        ret, keyring, err = self.mgr.check_mon_command({
-            'prefix': 'auth get-or-create',
-            'entity': self.get_auth_entity(igw_id),
-            'caps': ['mon', 'profile rbd, '
-                            'allow command "osd blacklist", '
-                            'allow command "config-key get" with "key" prefix "iscsi/"',
-                     'osd', 'allow rwx'],
-        })
+        keyring = self.get_keyring_with_caps(self.get_auth_entity(igw_id),
+                                             ['mon', 'profile rbd, '
+                                              'allow command "osd blocklist", '
+                                              'allow command "config-key get" with "key" prefix "iscsi/"',
+                                              'mgr', 'allow command "service status"',
+                                              'osd', 'allow rwx'])
 
         if spec.ssl_cert:
             if isinstance(spec.ssl_cert, list):
@@ -62,23 +58,32 @@ class IscsiService(CephService):
                 'val': key_data,
             })
 
+        # add active mgr ip address to trusted list so dashboard can access
+        trusted_ip_list = spec.trusted_ip_list if spec.trusted_ip_list else ''
+        if trusted_ip_list:
+            trusted_ip_list += ','
+        trusted_ip_list += self.mgr.get_mgr_ip()
+
         context = {
             'client_name': '{}.{}'.format(utils.name_to_config_section('iscsi'), igw_id),
+            'trusted_ip_list': trusted_ip_list,
             'spec': spec
         }
         igw_conf = self.mgr.template.render('services/iscsi/iscsi-gateway.cfg.j2', context)
 
         daemon_spec.keyring = keyring
         daemon_spec.extra_files = {'iscsi-gateway.cfg': igw_conf}
-
+        daemon_spec.final_config, daemon_spec.deps = self.generate_config(daemon_spec)
+        daemon_spec.deps = [self.mgr.get_mgr_ip()]
         return daemon_spec
 
     def config_dashboard(self, daemon_descrs: List[DaemonDescription]) -> None:
         def get_set_cmd_dicts(out: str) -> List[dict]:
             gateways = json.loads(out)['gateways']
             cmd_dicts = []
+            # TODO: fail, if we don't have a spec
             spec = cast(IscsiServiceSpec,
-                        self.mgr.spec_store.specs.get(daemon_descrs[0].service_name(), None))
+                        self.mgr.spec_store.all_specs.get(daemon_descrs[0].service_name(), None))
             if spec.api_secure and spec.ssl_cert and spec.ssl_key:
                 cmd_dicts.append({
                     'prefix': 'dashboard set-iscsi-api-ssl-verification',
@@ -90,12 +95,17 @@ class IscsiService(CephService):
                     'value': "true"
                 })
             for dd in daemon_descrs:
+                assert dd.hostname is not None
+                # todo: this can fail:
                 spec = cast(IscsiServiceSpec,
-                            self.mgr.spec_store.specs.get(dd.service_name(), None))
+                            self.mgr.spec_store.all_specs.get(dd.service_name(), None))
                 if not spec:
                     logger.warning('No ServiceSpec found for %s', dd)
                     continue
-                ip = utils.resolve_ip(dd.hostname)
+                ip = utils.resolve_ip(self.mgr.inventory.get_addr(dd.hostname))
+                # IPv6 URL encoding requires square brackets enclosing the ip
+                if type(ip_address(ip)) is IPv6Address:
+                    ip = f'[{ip}]'
                 protocol = "http"
                 if spec.api_secure and spec.ssl_cert and spec.ssl_key:
                     protocol = "https"
@@ -118,3 +128,79 @@ class IscsiService(CephService):
             get_cmd='dashboard iscsi-gateway-list',
             get_set_cmd_dicts=get_set_cmd_dicts
         )
+
+    def ok_to_stop(self,
+                   daemon_ids: List[str],
+                   force: bool = False,
+                   known: Optional[List[str]] = None) -> HandleCommandResult:
+        # if only 1 iscsi, alert user (this is not passable with --force)
+        warn, warn_message = self._enough_daemons_to_stop(self.TYPE, daemon_ids, 'Iscsi', 1, True)
+        if warn:
+            return HandleCommandResult(-errno.EBUSY, '', warn_message)
+
+        # if reached here, there is > 1 nfs daemon. make sure none are down
+        warn_message = (
+            'ALERT: 1 iscsi daemon is already down. Please bring it back up before stopping this one')
+        iscsi_daemons = self.mgr.cache.get_daemons_by_type(self.TYPE)
+        for i in iscsi_daemons:
+            if i.status != DaemonDescriptionStatus.running:
+                return HandleCommandResult(-errno.EBUSY, '', warn_message)
+
+        names = [f'{self.TYPE}.{d_id}' for d_id in daemon_ids]
+        warn_message = f'It is presumed safe to stop {names}'
+        return HandleCommandResult(0, warn_message, '')
+
+    def post_remove(self, daemon: DaemonDescription, is_failed_deploy: bool) -> None:
+        """
+        Called after the daemon is removed.
+        """
+        logger.debug(f'Post remove daemon {self.TYPE}.{daemon.daemon_id}')
+
+        # remove config for dashboard iscsi gateways
+        ret, out, err = self.mgr.mon_command({
+            'prefix': 'dashboard iscsi-gateway-rm',
+            'name': daemon.hostname,
+        })
+        if not ret:
+            logger.info(f'{daemon.hostname} removed from iscsi gateways dashboard config')
+
+        # needed to know if we have ssl stuff for iscsi in ceph config
+        iscsi_config_dict = {}
+        ret, iscsi_config, err = self.mgr.mon_command({
+            'prefix': 'config-key dump',
+            'key': 'iscsi',
+        })
+        if iscsi_config:
+            iscsi_config_dict = json.loads(iscsi_config)
+
+        # remove iscsi cert and key from ceph config
+        for iscsi_key, value in iscsi_config_dict.items():
+            if f'iscsi/client.{daemon.name()}/' in iscsi_key:
+                ret, out, err = self.mgr.mon_command({
+                    'prefix': 'config-key rm',
+                    'key': iscsi_key,
+                })
+                logger.info(f'{iscsi_key} removed from ceph config')
+
+    def purge(self, service_name: str) -> None:
+        """Removes configuration
+        """
+        spec = cast(IscsiServiceSpec, self.mgr.spec_store[service_name].spec)
+        try:
+            # remove service configuration from the pool
+            try:
+                subprocess.run(['rados',
+                                '-k', str(self.mgr.get_ceph_option('keyring')),
+                                '-n', f'mgr.{self.mgr.get_mgr_id()}',
+                                '-p', cast(str, spec.pool),
+                                'rm',
+                                'gateway.conf'],
+                               timeout=5)
+                logger.info(f'<gateway.conf> removed from {spec.pool}')
+            except subprocess.CalledProcessError as ex:
+                logger.error(f'Error executing <<{ex.cmd}>>: {ex.output}')
+            except subprocess.TimeoutExpired:
+                logger.error(f'timeout (5s) trying to remove <gateway.conf> from {spec.pool}')
+
+        except Exception:
+            logger.exception(f'failed to purge {service_name}')