]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/cephadm/services/iscsi.py
import ceph 16.2.6
[ceph.git] / ceph / src / pybind / mgr / cephadm / services / iscsi.py
CommitLineData
f67539c2 1import errno
e306af50
TL
2import json
3import logging
b3b6e05e 4import subprocess
f67539c2
TL
5from typing import List, cast, Optional
6from ipaddress import ip_address, IPv6Address
e306af50 7
f67539c2 8from mgr_module import HandleCommandResult
e306af50
TL
9from ceph.deployment.service_spec import IscsiServiceSpec
10
f67539c2
TL
11from orchestrator import DaemonDescription, DaemonDescriptionStatus
12from .cephadmservice import CephadmDaemonDeploySpec, CephService
e306af50
TL
13from .. import utils
14
15logger = logging.getLogger(__name__)
16
17
f91f0fd5 18class IscsiService(CephService):
f6b5b4d7
TL
19 TYPE = 'iscsi'
20
522d829b 21 def config(self, spec: IscsiServiceSpec) -> None: # type: ignore
f6b5b4d7 22 assert self.TYPE == spec.service_type
adb31ebb 23 assert spec.pool
e306af50
TL
24 self.mgr._check_pool_exists(spec.pool, spec.service_name())
25
f67539c2 26 def prepare_create(self, daemon_spec: CephadmDaemonDeploySpec) -> CephadmDaemonDeploySpec:
f6b5b4d7 27 assert self.TYPE == daemon_spec.daemon_type
f6b5b4d7 28
f67539c2 29 spec = cast(IscsiServiceSpec, self.mgr.spec_store[daemon_spec.service_name].spec)
f6b5b4d7
TL
30 igw_id = daemon_spec.daemon_id
31
f67539c2
TL
32 keyring = self.get_keyring_with_caps(self.get_auth_entity(igw_id),
33 ['mon', 'profile rbd, '
34 'allow command "osd blocklist", '
35 'allow command "config-key get" with "key" prefix "iscsi/"',
36 'mgr', 'allow command "service status"',
37 'osd', 'allow rwx'])
e306af50
TL
38
39 if spec.ssl_cert:
40 if isinstance(spec.ssl_cert, list):
41 cert_data = '\n'.join(spec.ssl_cert)
42 else:
43 cert_data = spec.ssl_cert
f91f0fd5 44 ret, out, err = self.mgr.check_mon_command({
e306af50
TL
45 'prefix': 'config-key set',
46 'key': f'iscsi/{utils.name_to_config_section("iscsi")}.{igw_id}/iscsi-gateway.crt',
47 'val': cert_data,
48 })
49
50 if spec.ssl_key:
51 if isinstance(spec.ssl_key, list):
52 key_data = '\n'.join(spec.ssl_key)
53 else:
54 key_data = spec.ssl_key
f91f0fd5 55 ret, out, err = self.mgr.check_mon_command({
e306af50
TL
56 'prefix': 'config-key set',
57 'key': f'iscsi/{utils.name_to_config_section("iscsi")}.{igw_id}/iscsi-gateway.key',
58 'val': key_data,
59 })
60
61 context = {
62 'client_name': '{}.{}'.format(utils.name_to_config_section('iscsi'), igw_id),
63 'spec': spec
64 }
65 igw_conf = self.mgr.template.render('services/iscsi/iscsi-gateway.cfg.j2', context)
f6b5b4d7
TL
66
67 daemon_spec.keyring = keyring
f91f0fd5 68 daemon_spec.extra_files = {'iscsi-gateway.cfg': igw_conf}
f6b5b4d7 69
f67539c2
TL
70 daemon_spec.final_config, daemon_spec.deps = self.generate_config(daemon_spec)
71
f91f0fd5 72 return daemon_spec
f6b5b4d7 73
f91f0fd5 74 def config_dashboard(self, daemon_descrs: List[DaemonDescription]) -> None:
f6b5b4d7
TL
75 def get_set_cmd_dicts(out: str) -> List[dict]:
76 gateways = json.loads(out)['gateways']
77 cmd_dicts = []
f67539c2 78 # TODO: fail, if we don't have a spec
adb31ebb 79 spec = cast(IscsiServiceSpec,
f67539c2 80 self.mgr.spec_store.all_specs.get(daemon_descrs[0].service_name(), None))
adb31ebb
TL
81 if spec.api_secure and spec.ssl_cert and spec.ssl_key:
82 cmd_dicts.append({
83 'prefix': 'dashboard set-iscsi-api-ssl-verification',
84 'value': "false"
85 })
86 else:
87 cmd_dicts.append({
88 'prefix': 'dashboard set-iscsi-api-ssl-verification',
89 'value': "true"
90 })
f6b5b4d7 91 for dd in daemon_descrs:
f67539c2
TL
92 assert dd.hostname is not None
93 # todo: this can fail:
f6b5b4d7 94 spec = cast(IscsiServiceSpec,
f67539c2 95 self.mgr.spec_store.all_specs.get(dd.service_name(), None))
f6b5b4d7
TL
96 if not spec:
97 logger.warning('No ServiceSpec found for %s', dd)
98 continue
b3b6e05e 99 ip = utils.resolve_ip(self.mgr.inventory.get_addr(dd.hostname))
f67539c2
TL
100 # IPv6 URL encoding requires square brackets enclosing the ip
101 if type(ip_address(ip)) is IPv6Address:
102 ip = f'[{ip}]'
adb31ebb
TL
103 protocol = "http"
104 if spec.api_secure and spec.ssl_cert and spec.ssl_key:
105 protocol = "https"
106 service_url = '{}://{}:{}@{}:{}'.format(
107 protocol, spec.api_user or 'admin', spec.api_password or 'admin', ip, spec.api_port or '5000')
108 gw = gateways.get(dd.hostname)
f6b5b4d7 109 if not gw or gw['service_url'] != service_url:
adb31ebb
TL
110 safe_service_url = '{}://{}:{}@{}:{}'.format(
111 protocol, '<api-user>', '<api-password>', ip, spec.api_port or '5000')
112 logger.info('Adding iSCSI gateway %s to Dashboard', safe_service_url)
f6b5b4d7 113 cmd_dicts.append({
e306af50 114 'prefix': 'dashboard iscsi-gateway-add',
cd265ab1 115 'inbuf': service_url,
adb31ebb 116 'name': dd.hostname
e306af50 117 })
f6b5b4d7
TL
118 return cmd_dicts
119
120 self._check_and_set_dashboard(
121 service_name='iSCSI',
122 get_cmd='dashboard iscsi-gateway-list',
123 get_set_cmd_dicts=get_set_cmd_dicts
124 )
f67539c2
TL
125
126 def ok_to_stop(self,
127 daemon_ids: List[str],
128 force: bool = False,
129 known: Optional[List[str]] = None) -> HandleCommandResult:
130 # if only 1 iscsi, alert user (this is not passable with --force)
131 warn, warn_message = self._enough_daemons_to_stop(self.TYPE, daemon_ids, 'Iscsi', 1, True)
132 if warn:
133 return HandleCommandResult(-errno.EBUSY, '', warn_message)
134
135 # if reached here, there is > 1 nfs daemon. make sure none are down
136 warn_message = (
137 'ALERT: 1 iscsi daemon is already down. Please bring it back up before stopping this one')
138 iscsi_daemons = self.mgr.cache.get_daemons_by_type(self.TYPE)
139 for i in iscsi_daemons:
140 if i.status != DaemonDescriptionStatus.running:
141 return HandleCommandResult(-errno.EBUSY, '', warn_message)
142
143 names = [f'{self.TYPE}.{d_id}' for d_id in daemon_ids]
144 warn_message = f'It is presumed safe to stop {names}'
145 return HandleCommandResult(0, warn_message, '')
b3b6e05e
TL
146
147 def post_remove(self, daemon: DaemonDescription) -> None:
148 """
149 Called after the daemon is removed.
150 """
151 logger.debug(f'Post remove daemon {self.TYPE}.{daemon.daemon_id}')
152
522d829b
TL
153 if 'dashboard' in self.mgr.get('mgr_map')['modules']:
154 # remove config for dashboard iscsi gateways
155 ret, out, err = self.mgr.check_mon_command({
156 'prefix': 'dashboard iscsi-gateway-rm',
157 'name': daemon.hostname,
158 })
159 logger.info(f'{daemon.hostname} removed from iscsi gateways dashboard config')
b3b6e05e
TL
160
161 # needed to know if we have ssl stuff for iscsi in ceph config
162 iscsi_config_dict = {}
163 ret, iscsi_config, err = self.mgr.check_mon_command({
164 'prefix': 'config-key dump',
165 'key': 'iscsi',
166 })
167 if iscsi_config:
168 iscsi_config_dict = json.loads(iscsi_config)
169
170 # remove iscsi cert and key from ceph config
171 for iscsi_key, value in iscsi_config_dict.items():
172 if f'iscsi/client.{daemon.name()}/' in iscsi_key:
173 ret, out, err = self.mgr.check_mon_command({
174 'prefix': 'config-key rm',
175 'key': iscsi_key,
176 })
177 logger.info(f'{iscsi_key} removed from ceph config')
178
179 def purge(self, service_name: str) -> None:
180 """Removes configuration
181 """
182 spec = cast(IscsiServiceSpec, self.mgr.spec_store[service_name].spec)
183 try:
184 # remove service configuration from the pool
185 try:
186 subprocess.run(['rados',
187 '-k', str(self.mgr.get_ceph_option('keyring')),
188 '-n', f'mgr.{self.mgr.get_mgr_id()}',
189 '-p', cast(str, spec.pool),
190 'rm',
191 'gateway.conf'],
192 timeout=5)
193 logger.info(f'<gateway.conf> removed from {spec.pool}')
194 except subprocess.CalledProcessError as ex:
195 logger.error(f'Error executing <<{ex.cmd}>>: {ex.output}')
196 except subprocess.TimeoutExpired:
197 logger.error(f'timeout (5s) trying to remove <gateway.conf> from {spec.pool}')
198
199 except Exception:
200 logger.exception(f'failed to purge {service_name}')