]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/cephadm/services/nfs.py
import 15.2.5
[ceph.git] / ceph / src / pybind / mgr / cephadm / services / nfs.py
1 import logging
2 from typing import TYPE_CHECKING, Dict, Optional, Tuple, Any, List, Set, cast
3
4 from ceph.deployment.service_spec import NFSServiceSpec
5 import rados
6
7 from orchestrator import OrchestratorError, DaemonDescription
8
9 from cephadm import utils
10 from cephadm.services.cephadmservice import CephadmService, CephadmDaemonSpec
11
12 if TYPE_CHECKING:
13 from cephadm.module import CephadmOrchestrator
14
15 logger = logging.getLogger(__name__)
16
17
18 class NFSService(CephadmService):
19 TYPE = 'nfs'
20
21 def generate_config(self, daemon_spec: CephadmDaemonSpec) -> Tuple[Dict[str, Any], List[str]]:
22 assert self.TYPE == daemon_spec.daemon_type
23
24 daemon_type = daemon_spec.daemon_type
25 daemon_id = daemon_spec.daemon_id
26 host = daemon_spec.host
27
28 deps: List[str] = []
29
30 # find the matching NFSServiceSpec
31 # TODO: find the spec and pass via _create_daemon instead ??
32 dd = DaemonDescription()
33 dd.daemon_type = daemon_type
34 dd.daemon_id = daemon_id
35 dd.hostname = host
36
37 service_name = dd.service_name()
38 specs = self.mgr.spec_store.find(service_name)
39
40 if not specs:
41 raise OrchestratorError('Cannot find service spec %s' % (service_name))
42 elif len(specs) > 1:
43 raise OrchestratorError('Found multiple service specs for %s' % (service_name))
44 else:
45 # cast to keep mypy happy
46 spec = cast(NFSServiceSpec, specs[0])
47
48 nfs = NFSGanesha(self.mgr, daemon_id, spec)
49
50 # create the keyring
51 entity = nfs.get_keyring_entity()
52 keyring = nfs.get_or_create_keyring(entity=entity)
53
54 # update the caps after get-or-create, the keyring might already exist!
55 nfs.update_keyring_caps(entity=entity)
56
57 # create the rados config object
58 nfs.create_rados_config_obj()
59
60 # generate the cephadm config
61 cephadm_config = nfs.get_cephadm_config()
62 cephadm_config.update(
63 self.mgr._get_config_and_keyring(
64 daemon_type, daemon_id,
65 keyring=keyring,
66 host=host
67 )
68 )
69
70 return cephadm_config, deps
71
72 def config(self, spec: NFSServiceSpec) -> None:
73 assert self.TYPE == spec.service_type
74 self.mgr._check_pool_exists(spec.pool, spec.service_name())
75
76 logger.info('Saving service %s spec with placement %s' % (
77 spec.service_name(), spec.placement.pretty_str()))
78 self.mgr.spec_store.save(spec)
79
80 def create(self, daemon_spec: CephadmDaemonSpec[NFSServiceSpec]) -> str:
81 assert self.TYPE == daemon_spec.daemon_type
82 assert daemon_spec.spec
83
84 daemon_id = daemon_spec.daemon_id
85 host = daemon_spec.host
86 spec = daemon_spec.spec
87
88 logger.info('Create daemon %s on host %s with spec %s' % (
89 daemon_id, host, spec))
90 return self.mgr._create_daemon(daemon_spec)
91
92 def config_dashboard(self, daemon_descrs: List[DaemonDescription]):
93
94 def get_set_cmd_dicts(out: str) -> List[dict]:
95 locations: Set[str] = set()
96 for dd in daemon_descrs:
97 spec = cast(NFSServiceSpec,
98 self.mgr.spec_store.specs.get(dd.service_name(), None))
99 if not spec or not spec.service_id:
100 logger.warning('No ServiceSpec or service_id found for %s', dd)
101 continue
102 location = '{}:{}'.format(spec.service_id, spec.pool)
103 if spec.namespace:
104 location = '{}/{}'.format(location, spec.namespace)
105 locations.add(location)
106 new_value = ','.join(locations)
107 if new_value and new_value != out:
108 return [{'prefix': 'dashboard set-ganesha-clusters-rados-pool-namespace',
109 'value': new_value}]
110 return []
111
112 self._check_and_set_dashboard(
113 service_name='Ganesha',
114 get_cmd='dashboard get-ganesha-clusters-rados-pool-namespace',
115 get_set_cmd_dicts=get_set_cmd_dicts
116 )
117
118
119 class NFSGanesha(object):
120 def __init__(self,
121 mgr: "CephadmOrchestrator",
122 daemon_id: str,
123 spec: NFSServiceSpec) -> None:
124 assert spec.service_id and daemon_id.startswith(spec.service_id)
125 mgr._check_pool_exists(spec.pool, spec.service_name())
126
127 self.mgr = mgr
128 self.daemon_id = daemon_id
129 self.spec = spec
130
131 def get_daemon_name(self) -> str:
132 return '%s.%s' % (self.spec.service_type, self.daemon_id)
133
134 def get_rados_user(self) -> str:
135 return '%s.%s' % (self.spec.service_type, self.daemon_id)
136
137 def get_keyring_entity(self) -> str:
138 return utils.name_to_config_section(self.get_rados_user())
139
140 def get_or_create_keyring(self, entity: Optional[str] = None) -> str:
141 if not entity:
142 entity = self.get_keyring_entity()
143
144 logger.info('Create keyring: %s' % entity)
145 ret, keyring, err = self.mgr.mon_command({
146 'prefix': 'auth get-or-create',
147 'entity': entity,
148 })
149
150 if ret != 0:
151 raise OrchestratorError(
152 'Unable to create keyring %s: %s %s'
153 % (entity, ret, err))
154 return keyring
155
156 def update_keyring_caps(self, entity: Optional[str] = None) -> None:
157 if not entity:
158 entity = self.get_keyring_entity()
159
160 osd_caps = 'allow rw pool=%s' % (self.spec.pool)
161 if self.spec.namespace:
162 osd_caps = '%s namespace=%s' % (osd_caps, self.spec.namespace)
163
164 logger.info('Updating keyring caps: %s' % entity)
165 ret, out, err = self.mgr.mon_command({
166 'prefix': 'auth caps',
167 'entity': entity,
168 'caps': ['mon', 'allow r',
169 'osd', osd_caps],
170 })
171
172 if ret != 0:
173 raise OrchestratorError(
174 'Unable to update keyring caps %s: %s %s'
175 % (entity, ret, err))
176
177 def create_rados_config_obj(self, clobber: Optional[bool] = False) -> None:
178 with self.mgr.rados.open_ioctx(self.spec.pool) as ioctx:
179 if self.spec.namespace:
180 ioctx.set_namespace(self.spec.namespace)
181
182 obj = self.spec.rados_config_name()
183 exists = True
184 try:
185 ioctx.stat(obj)
186 except rados.ObjectNotFound as e:
187 exists = False
188
189 if exists and not clobber:
190 # Assume an existing config
191 logger.info('Rados config object exists: %s' % obj)
192 else:
193 # Create an empty config object
194 logger.info('Creating rados config object: %s' % obj)
195 ioctx.write_full(obj, ''.encode('utf-8'))
196
197 def get_ganesha_conf(self) -> str:
198 context = dict(user=self.get_rados_user(),
199 nodeid=self.get_daemon_name(),
200 pool=self.spec.pool,
201 namespace=self.spec.namespace if self.spec.namespace else '',
202 url=self.spec.rados_config_location())
203 return self.mgr.template.render('services/nfs/ganesha.conf.j2', context)
204
205 def get_cephadm_config(self) -> Dict[str, Any]:
206 config: Dict[str, Any] = {'pool': self.spec.pool}
207 if self.spec.namespace:
208 config['namespace'] = self.spec.namespace
209 config['userid'] = self.get_rados_user()
210 config['extra_args'] = ['-N', 'NIV_EVENT']
211 config['files'] = {
212 'ganesha.conf': self.get_ganesha_conf(),
213 }
214 logger.debug('Generated cephadm config-json: %s' % config)
215 return config