]>
git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/cephadm/services/iscsi.py
5 from typing
import List
, cast
, Optional
6 from ipaddress
import ip_address
, IPv6Address
8 from mgr_module
import HandleCommandResult
9 from ceph
.deployment
.service_spec
import IscsiServiceSpec
11 from orchestrator
import DaemonDescription
, DaemonDescriptionStatus
12 from .cephadmservice
import CephadmDaemonDeploySpec
, CephService
15 logger
= logging
.getLogger(__name__
)
18 class IscsiService(CephService
):
21 def config(self
, spec
: IscsiServiceSpec
) -> None: # type: ignore
22 assert self
.TYPE
== spec
.service_type
24 self
.mgr
._check
_pool
_exists
(spec
.pool
, spec
.service_name())
26 def get_trusted_ips(self
, spec
: IscsiServiceSpec
) -> str:
27 # add active mgr ip address to trusted list so dashboard can access
28 trusted_ip_list
= spec
.trusted_ip_list
if spec
.trusted_ip_list
else ''
29 mgr_ip
= self
.mgr
.get_mgr_ip()
30 if mgr_ip
not in [s
.strip() for s
in trusted_ip_list
.split(',')]:
32 trusted_ip_list
+= ','
33 trusted_ip_list
+= mgr_ip
34 return trusted_ip_list
36 def prepare_create(self
, daemon_spec
: CephadmDaemonDeploySpec
) -> CephadmDaemonDeploySpec
:
37 assert self
.TYPE
== daemon_spec
.daemon_type
39 spec
= cast(IscsiServiceSpec
, self
.mgr
.spec_store
[daemon_spec
.service_name
].spec
)
40 igw_id
= daemon_spec
.daemon_id
42 keyring
= self
.get_keyring_with_caps(self
.get_auth_entity(igw_id
),
43 ['mon', 'profile rbd, '
44 'allow command "osd blocklist", '
45 'allow command "config-key get" with "key" prefix "iscsi/"',
46 'mgr', 'allow command "service status"',
50 if isinstance(spec
.ssl_cert
, list):
51 cert_data
= '\n'.join(spec
.ssl_cert
)
53 cert_data
= spec
.ssl_cert
54 ret
, out
, err
= self
.mgr
.check_mon_command({
55 'prefix': 'config-key set',
56 'key': f
'iscsi/{utils.name_to_config_section("iscsi")}.{igw_id}/iscsi-gateway.crt',
61 if isinstance(spec
.ssl_key
, list):
62 key_data
= '\n'.join(spec
.ssl_key
)
64 key_data
= spec
.ssl_key
65 ret
, out
, err
= self
.mgr
.check_mon_command({
66 'prefix': 'config-key set',
67 'key': f
'iscsi/{utils.name_to_config_section("iscsi")}.{igw_id}/iscsi-gateway.key',
71 trusted_ip_list
= self
.get_trusted_ips(spec
)
74 'client_name': '{}.{}'.format(utils
.name_to_config_section('iscsi'), igw_id
),
75 'trusted_ip_list': trusted_ip_list
,
78 igw_conf
= self
.mgr
.template
.render('services/iscsi/iscsi-gateway.cfg.j2', context
)
80 daemon_spec
.keyring
= keyring
81 daemon_spec
.extra_files
= {'iscsi-gateway.cfg': igw_conf
}
82 daemon_spec
.final_config
, daemon_spec
.deps
= self
.generate_config(daemon_spec
)
83 daemon_spec
.deps
= [trusted_ip_list
]
86 def config_dashboard(self
, daemon_descrs
: List
[DaemonDescription
]) -> None:
87 def get_set_cmd_dicts(out
: str) -> List
[dict]:
88 gateways
= json
.loads(out
)['gateways']
90 # TODO: fail, if we don't have a spec
91 spec
= cast(IscsiServiceSpec
,
92 self
.mgr
.spec_store
.all_specs
.get(daemon_descrs
[0].service_name(), None))
93 if spec
.api_secure
and spec
.ssl_cert
and spec
.ssl_key
:
95 'prefix': 'dashboard set-iscsi-api-ssl-verification',
100 'prefix': 'dashboard set-iscsi-api-ssl-verification',
103 for dd
in daemon_descrs
:
104 assert dd
.hostname
is not None
105 # todo: this can fail:
106 spec
= cast(IscsiServiceSpec
,
107 self
.mgr
.spec_store
.all_specs
.get(dd
.service_name(), None))
109 logger
.warning('No ServiceSpec found for %s', dd
)
111 ip
= utils
.resolve_ip(self
.mgr
.inventory
.get_addr(dd
.hostname
))
112 # IPv6 URL encoding requires square brackets enclosing the ip
113 if type(ip_address(ip
)) is IPv6Address
:
116 if spec
.api_secure
and spec
.ssl_cert
and spec
.ssl_key
:
118 service_url
= '{}://{}:{}@{}:{}'.format(
119 protocol
, spec
.api_user
or 'admin', spec
.api_password
or 'admin', ip
, spec
.api_port
or '5000')
120 gw
= gateways
.get(dd
.hostname
)
121 if not gw
or gw
['service_url'] != service_url
:
122 safe_service_url
= '{}://{}:{}@{}:{}'.format(
123 protocol
, '<api-user>', '<api-password>', ip
, spec
.api_port
or '5000')
124 logger
.info('Adding iSCSI gateway %s to Dashboard', safe_service_url
)
126 'prefix': 'dashboard iscsi-gateway-add',
127 'inbuf': service_url
,
132 self
._check
_and
_set
_dashboard
(
133 service_name
='iSCSI',
134 get_cmd
='dashboard iscsi-gateway-list',
135 get_set_cmd_dicts
=get_set_cmd_dicts
139 daemon_ids
: List
[str],
141 known
: Optional
[List
[str]] = None) -> HandleCommandResult
:
142 # if only 1 iscsi, alert user (this is not passable with --force)
143 warn
, warn_message
= self
._enough
_daemons
_to
_stop
(self
.TYPE
, daemon_ids
, 'Iscsi', 1, True)
145 return HandleCommandResult(-errno
.EBUSY
, '', warn_message
)
147 # if reached here, there is > 1 nfs daemon. make sure none are down
149 'ALERT: 1 iscsi daemon is already down. Please bring it back up before stopping this one')
150 iscsi_daemons
= self
.mgr
.cache
.get_daemons_by_type(self
.TYPE
)
151 for i
in iscsi_daemons
:
152 if i
.status
!= DaemonDescriptionStatus
.running
:
153 return HandleCommandResult(-errno
.EBUSY
, '', warn_message
)
155 names
= [f
'{self.TYPE}.{d_id}' for d_id
in daemon_ids
]
156 warn_message
= f
'It is presumed safe to stop {names}'
157 return HandleCommandResult(0, warn_message
, '')
159 def post_remove(self
, daemon
: DaemonDescription
, is_failed_deploy
: bool) -> None:
161 Called after the daemon is removed.
163 logger
.debug(f
'Post remove daemon {self.TYPE}.{daemon.daemon_id}')
165 # remove config for dashboard iscsi gateways
166 ret
, out
, err
= self
.mgr
.mon_command({
167 'prefix': 'dashboard iscsi-gateway-rm',
168 'name': daemon
.hostname
,
171 logger
.info(f
'{daemon.hostname} removed from iscsi gateways dashboard config')
173 # needed to know if we have ssl stuff for iscsi in ceph config
174 iscsi_config_dict
= {}
175 ret
, iscsi_config
, err
= self
.mgr
.mon_command({
176 'prefix': 'config-key dump',
180 iscsi_config_dict
= json
.loads(iscsi_config
)
182 # remove iscsi cert and key from ceph config
183 for iscsi_key
, value
in iscsi_config_dict
.items():
184 if f
'iscsi/client.{daemon.name()}/' in iscsi_key
:
185 ret
, out
, err
= self
.mgr
.mon_command({
186 'prefix': 'config-key rm',
189 logger
.info(f
'{iscsi_key} removed from ceph config')
191 def purge(self
, service_name
: str) -> None:
192 """Removes configuration
194 spec
= cast(IscsiServiceSpec
, self
.mgr
.spec_store
[service_name
].spec
)
196 # remove service configuration from the pool
198 subprocess
.run(['rados',
199 '-k', str(self
.mgr
.get_ceph_option('keyring')),
200 '-n', f
'mgr.{self.mgr.get_mgr_id()}',
201 '-p', cast(str, spec
.pool
),
205 logger
.info(f
'<gateway.conf> removed from {spec.pool}')
206 except subprocess
.CalledProcessError
as ex
:
207 logger
.error(f
'Error executing <<{ex.cmd}>>: {ex.output}')
208 except subprocess
.TimeoutExpired
:
209 logger
.error(f
'timeout (5s) trying to remove <gateway.conf> from {spec.pool}')
212 logger
.exception(f
'failed to purge {service_name}')