]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/nfs/cluster.py
import ceph 16.2.7
[ceph.git] / ceph / src / pybind / mgr / nfs / cluster.py
CommitLineData
b3b6e05e 1import logging
b3b6e05e
TL
2import json
3import re
a4b75251
TL
4import socket
5from typing import cast, Dict, List, Any, Union, Optional, TYPE_CHECKING, Tuple
b3b6e05e 6
a4b75251 7from mgr_module import NFS_POOL_NAME as POOL_NAME
b3b6e05e
TL
8from ceph.deployment.service_spec import NFSServiceSpec, PlacementSpec, IngressSpec
9
10import orchestrator
11
12from .exception import NFSInvalidOperation, ClusterNotFound
a4b75251 13from .utils import available_clusters, restart_nfs_service
b3b6e05e
TL
14from .export import NFSRados, exception_handler
15
a4b75251
TL
16if TYPE_CHECKING:
17 from nfs.module import Module
18 from mgr_module import MgrModule
19
20
b3b6e05e
TL
21log = logging.getLogger(__name__)
22
23
24def resolve_ip(hostname: str) -> str:
25 try:
26 r = socket.getaddrinfo(hostname, None, flags=socket.AI_CANONNAME,
27 type=socket.SOCK_STREAM)
28 # pick first v4 IP, if present
29 for a in r:
30 if a[0] == socket.AF_INET:
31 return a[4][0]
32 return r[0][4][0]
33 except socket.gaierror as e:
34 raise NFSInvalidOperation(f"Cannot resolve IP for host {hostname}: {e}")
35
36
a4b75251 37def create_ganesha_pool(mgr: 'MgrModule') -> None:
b3b6e05e 38 pool_list = [p['pool_name'] for p in mgr.get_osdmap().dump().get('pools', [])]
a4b75251
TL
39 if POOL_NAME not in pool_list:
40 mgr.check_mon_command({'prefix': 'osd pool create', 'pool': POOL_NAME})
b3b6e05e 41 mgr.check_mon_command({'prefix': 'osd pool application enable',
a4b75251 42 'pool': POOL_NAME,
b3b6e05e 43 'app': 'nfs'})
a4b75251 44 log.debug("Successfully created nfs-ganesha pool %s", POOL_NAME)
b3b6e05e
TL
45
46
47class NFSCluster:
a4b75251 48 def __init__(self, mgr: 'Module') -> None:
b3b6e05e
TL
49 self.mgr = mgr
50
a4b75251
TL
51 def _get_common_conf_obj_name(self, cluster_id: str) -> str:
52 return f'conf-nfs.{cluster_id}'
b3b6e05e 53
a4b75251
TL
54 def _get_user_conf_obj_name(self, cluster_id: str) -> str:
55 return f'userconf-nfs.{cluster_id}'
b3b6e05e 56
a4b75251
TL
57 def _call_orch_apply_nfs(
58 self,
59 cluster_id: str,
60 placement: Optional[str],
61 virtual_ip: Optional[str] = None,
62 port: Optional[int] = None,
63 ) -> None:
64 if not port:
65 port = 2049 # default nfs port
b3b6e05e
TL
66 if virtual_ip:
67 # nfs + ingress
68 # run NFS on non-standard port
a4b75251 69 spec = NFSServiceSpec(service_type='nfs', service_id=cluster_id,
b3b6e05e
TL
70 placement=PlacementSpec.from_string(placement),
71 # use non-default port so we don't conflict with ingress
a4b75251 72 port=10000 + port) # semi-arbitrary, fix me someday
b3b6e05e
TL
73 completion = self.mgr.apply_nfs(spec)
74 orchestrator.raise_if_exception(completion)
75 ispec = IngressSpec(service_type='ingress',
a4b75251
TL
76 service_id='nfs.' + cluster_id,
77 backend_service='nfs.' + cluster_id,
78 frontend_port=port,
79 monitor_port=7000 + port, # semi-arbitrary, fix me someday
b3b6e05e
TL
80 virtual_ip=virtual_ip)
81 completion = self.mgr.apply_ingress(ispec)
82 orchestrator.raise_if_exception(completion)
83 else:
84 # standalone nfs
a4b75251
TL
85 spec = NFSServiceSpec(service_type='nfs', service_id=cluster_id,
86 placement=PlacementSpec.from_string(placement),
87 port=port)
b3b6e05e
TL
88 completion = self.mgr.apply_nfs(spec)
89 orchestrator.raise_if_exception(completion)
a4b75251 90 log.debug("Successfully deployed nfs daemons with cluster id %s and placement %s", cluster_id, placement)
b3b6e05e 91
a4b75251
TL
92 def create_empty_rados_obj(self, cluster_id: str) -> None:
93 common_conf = self._get_common_conf_obj_name(cluster_id)
94 NFSRados(self.mgr, cluster_id).write_obj('', self._get_common_conf_obj_name(cluster_id))
95 log.info("Created empty object:%s", common_conf)
96
97 def delete_config_obj(self, cluster_id: str) -> None:
98 NFSRados(self.mgr, cluster_id).remove_all_obj()
99 log.info("Deleted %s object and all objects in %s",
100 self._get_common_conf_obj_name(cluster_id), cluster_id)
101
102 def create_nfs_cluster(
103 self,
104 cluster_id: str,
105 placement: Optional[str],
106 virtual_ip: Optional[str],
107 ingress: Optional[bool] = None,
108 port: Optional[int] = None,
109 ) -> Tuple[int, str, str]:
b3b6e05e
TL
110 try:
111 if virtual_ip and not ingress:
112 raise NFSInvalidOperation('virtual_ip can only be provided with ingress enabled')
113 if not virtual_ip and ingress:
114 raise NFSInvalidOperation('ingress currently requires a virtual_ip')
115 invalid_str = re.search('[^A-Za-z0-9-_.]', cluster_id)
116 if invalid_str:
117 raise NFSInvalidOperation(f"cluster id {cluster_id} is invalid. "
118 f"{invalid_str.group()} is char not permitted")
119
a4b75251 120 create_ganesha_pool(self.mgr)
b3b6e05e 121
a4b75251 122 self.create_empty_rados_obj(cluster_id)
b3b6e05e
TL
123
124 if cluster_id not in available_clusters(self.mgr):
a4b75251 125 self._call_orch_apply_nfs(cluster_id, placement, virtual_ip, port)
b3b6e05e
TL
126 return 0, "NFS Cluster Created Successfully", ""
127 return 0, "", f"{cluster_id} cluster already exists"
128 except Exception as e:
129 return exception_handler(e, f"NFS Cluster {cluster_id} could not be created")
130
a4b75251 131 def delete_nfs_cluster(self, cluster_id: str) -> Tuple[int, str, str]:
b3b6e05e
TL
132 try:
133 cluster_list = available_clusters(self.mgr)
134 if cluster_id in cluster_list:
135 self.mgr.export_mgr.delete_all_exports(cluster_id)
a4b75251 136 completion = self.mgr.remove_service('ingress.nfs.' + cluster_id)
b3b6e05e 137 orchestrator.raise_if_exception(completion)
a4b75251 138 completion = self.mgr.remove_service('nfs.' + cluster_id)
b3b6e05e 139 orchestrator.raise_if_exception(completion)
a4b75251 140 self.delete_config_obj(cluster_id)
b3b6e05e
TL
141 return 0, "NFS Cluster Deleted Successfully", ""
142 return 0, "", "Cluster does not exist"
143 except Exception as e:
144 return exception_handler(e, f"Failed to delete NFS Cluster {cluster_id}")
145
a4b75251 146 def list_nfs_cluster(self) -> Tuple[int, str, str]:
b3b6e05e
TL
147 try:
148 return 0, '\n'.join(available_clusters(self.mgr)), ""
149 except Exception as e:
150 return exception_handler(e, "Failed to list NFS Cluster")
151
152 def _show_nfs_cluster_info(self, cluster_id: str) -> Dict[str, Any]:
b3b6e05e 153 completion = self.mgr.list_daemons(daemon_type='nfs')
b3b6e05e 154 # Here completion.result is a list DaemonDescription objects
a4b75251
TL
155 clusters = orchestrator.raise_if_exception(completion)
156 backends: List[Dict[str, Union[Any]]] = []
157
158 for cluster in clusters:
159 if cluster_id == cluster.service_id():
160 assert cluster.hostname
b3b6e05e
TL
161 try:
162 if cluster.ip:
163 ip = cluster.ip
164 else:
165 c = self.mgr.get_hosts()
166 orchestrator.raise_if_exception(c)
a4b75251 167 hosts = [h for h in c.result or []
b3b6e05e
TL
168 if h.hostname == cluster.hostname]
169 if hosts:
170 ip = resolve_ip(hosts[0].addr)
171 else:
172 # sigh
173 ip = resolve_ip(cluster.hostname)
174 backends.append({
a4b75251
TL
175 "hostname": cluster.hostname,
176 "ip": ip,
177 "port": cluster.ports[0] if cluster.ports else None
178 })
b3b6e05e
TL
179 except orchestrator.OrchestratorError:
180 continue
181
182 r: Dict[str, Any] = {
183 'virtual_ip': None,
184 'backend': backends,
185 }
186 sc = self.mgr.describe_service(service_type='ingress')
a4b75251
TL
187 services = orchestrator.raise_if_exception(sc)
188 for i in services:
b3b6e05e
TL
189 spec = cast(IngressSpec, i.spec)
190 if spec.backend_service == f'nfs.{cluster_id}':
a4b75251 191 r['virtual_ip'] = i.virtual_ip.split('/')[0] if i.virtual_ip else None
b3b6e05e
TL
192 if i.ports:
193 r['port'] = i.ports[0]
194 if len(i.ports) > 1:
195 r['monitor_port'] = i.ports[1]
a4b75251 196 log.debug("Successfully fetched %s info: %s", cluster_id, r)
b3b6e05e
TL
197 return r
198
a4b75251 199 def show_nfs_cluster_info(self, cluster_id: Optional[str] = None) -> Tuple[int, str, str]:
b3b6e05e 200 try:
b3b6e05e
TL
201 info_res = {}
202 if cluster_id:
203 cluster_ls = [cluster_id]
204 else:
205 cluster_ls = available_clusters(self.mgr)
206
207 for cluster_id in cluster_ls:
208 res = self._show_nfs_cluster_info(cluster_id)
209 if res:
210 info_res[cluster_id] = res
211 return (0, json.dumps(info_res, indent=4), '')
212 except Exception as e:
213 return exception_handler(e, "Failed to show info for cluster")
214
a4b75251
TL
215 def get_nfs_cluster_config(self, cluster_id: str) -> Tuple[int, str, str]:
216 try:
217 if cluster_id in available_clusters(self.mgr):
218 rados_obj = NFSRados(self.mgr, cluster_id)
219 conf = rados_obj.read_obj(self._get_user_conf_obj_name(cluster_id))
220 return 0, conf or "", ""
221 raise ClusterNotFound()
222 except Exception as e:
223 return exception_handler(e, f"Fetching NFS-Ganesha Config failed for {cluster_id}")
224
225 def set_nfs_cluster_config(self, cluster_id: str, nfs_config: str) -> Tuple[int, str, str]:
b3b6e05e 226 try:
b3b6e05e 227 if cluster_id in available_clusters(self.mgr):
a4b75251 228 rados_obj = NFSRados(self.mgr, cluster_id)
b3b6e05e
TL
229 if rados_obj.check_user_config():
230 return 0, "", "NFS-Ganesha User Config already exists"
a4b75251
TL
231 rados_obj.write_obj(nfs_config, self._get_user_conf_obj_name(cluster_id),
232 self._get_common_conf_obj_name(cluster_id))
233 log.debug("Successfully saved %s's user config: \n %s", cluster_id, nfs_config)
b3b6e05e
TL
234 restart_nfs_service(self.mgr, cluster_id)
235 return 0, "NFS-Ganesha Config Set Successfully", ""
236 raise ClusterNotFound()
237 except NotImplementedError:
238 return 0, "NFS-Ganesha Config Added Successfully "\
a4b75251 239 "(Manual Restart of NFS PODS required)", ""
b3b6e05e
TL
240 except Exception as e:
241 return exception_handler(e, f"Setting NFS-Ganesha Config failed for {cluster_id}")
242
a4b75251 243 def reset_nfs_cluster_config(self, cluster_id: str) -> Tuple[int, str, str]:
b3b6e05e
TL
244 try:
245 if cluster_id in available_clusters(self.mgr):
a4b75251 246 rados_obj = NFSRados(self.mgr, cluster_id)
b3b6e05e
TL
247 if not rados_obj.check_user_config():
248 return 0, "", "NFS-Ganesha User Config does not exist"
a4b75251
TL
249 rados_obj.remove_obj(self._get_user_conf_obj_name(cluster_id),
250 self._get_common_conf_obj_name(cluster_id))
b3b6e05e
TL
251 restart_nfs_service(self.mgr, cluster_id)
252 return 0, "NFS-Ganesha Config Reset Successfully", ""
253 raise ClusterNotFound()
254 except NotImplementedError:
255 return 0, "NFS-Ganesha Config Removed Successfully "\
a4b75251 256 "(Manual Restart of NFS PODS required)", ""
b3b6e05e
TL
257 except Exception as e:
258 return exception_handler(e, f"Resetting NFS-Ganesha Config failed for {cluster_id}")