]>
git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/nfs/cluster.py
61bc477727baa5ba2d5e80c4697d512e7c4f4e23
6 from typing
import cast
, Dict
, List
, Any
, Union
, Optional
, TYPE_CHECKING
, Tuple
8 from mgr_module
import NFS_POOL_NAME
as POOL_NAME
9 from ceph
.deployment
.service_spec
import NFSServiceSpec
, PlacementSpec
, IngressSpec
13 from .exception
import NFSInvalidOperation
, ClusterNotFound
14 from .utils
import (available_clusters
, restart_nfs_service
, conf_obj_name
,
16 from .export
import NFSRados
, exception_handler
19 from nfs
.module
import Module
20 from mgr_module
import MgrModule
23 log
= logging
.getLogger(__name__
)
26 def resolve_ip(hostname
: str) -> str:
28 r
= socket
.getaddrinfo(hostname
, None, flags
=socket
.AI_CANONNAME
,
29 type=socket
.SOCK_STREAM
)
30 # pick first v4 IP, if present
32 if a
[0] == socket
.AF_INET
:
35 except socket
.gaierror
as e
:
36 raise NFSInvalidOperation(f
"Cannot resolve IP for host {hostname}: {e}")
39 def create_ganesha_pool(mgr
: 'MgrModule') -> None:
40 pool_list
= [p
['pool_name'] for p
in mgr
.get_osdmap().dump().get('pools', [])]
41 if POOL_NAME
not in pool_list
:
42 mgr
.check_mon_command({'prefix': 'osd pool create', 'pool': POOL_NAME
})
43 mgr
.check_mon_command({'prefix': 'osd pool application enable',
46 log
.debug("Successfully created nfs-ganesha pool %s", POOL_NAME
)
50 def __init__(self
, mgr
: 'Module') -> None:
53 def _call_orch_apply_nfs(
56 placement
: Optional
[str],
57 virtual_ip
: Optional
[str] = None,
58 port
: Optional
[int] = None,
61 port
= 2049 # default nfs port
64 # run NFS on non-standard port
65 spec
= NFSServiceSpec(service_type
='nfs', service_id
=cluster_id
,
66 placement
=PlacementSpec
.from_string(placement
),
67 # use non-default port so we don't conflict with ingress
68 port
=10000 + port
) # semi-arbitrary, fix me someday
69 completion
= self
.mgr
.apply_nfs(spec
)
70 orchestrator
.raise_if_exception(completion
)
71 ispec
= IngressSpec(service_type
='ingress',
72 service_id
='nfs.' + cluster_id
,
73 backend_service
='nfs.' + cluster_id
,
75 monitor_port
=7000 + port
, # semi-arbitrary, fix me someday
76 virtual_ip
=virtual_ip
)
77 completion
= self
.mgr
.apply_ingress(ispec
)
78 orchestrator
.raise_if_exception(completion
)
81 spec
= NFSServiceSpec(service_type
='nfs', service_id
=cluster_id
,
82 placement
=PlacementSpec
.from_string(placement
),
84 completion
= self
.mgr
.apply_nfs(spec
)
85 orchestrator
.raise_if_exception(completion
)
86 log
.debug("Successfully deployed nfs daemons with cluster id %s and placement %s",
87 cluster_id
, placement
)
89 def create_empty_rados_obj(self
, cluster_id
: str) -> None:
90 common_conf
= conf_obj_name(cluster_id
)
91 self
._rados
(cluster_id
).write_obj('', conf_obj_name(cluster_id
))
92 log
.info("Created empty object:%s", common_conf
)
94 def delete_config_obj(self
, cluster_id
: str) -> None:
95 self
._rados
(cluster_id
).remove_all_obj()
96 log
.info("Deleted %s object and all objects in %s",
97 conf_obj_name(cluster_id
), cluster_id
)
99 def create_nfs_cluster(
102 placement
: Optional
[str],
103 virtual_ip
: Optional
[str],
104 ingress
: Optional
[bool] = None,
105 port
: Optional
[int] = None,
106 ) -> Tuple
[int, str, str]:
110 # validate virtual_ip value: ip_address throws a ValueError
111 # exception in case it's not a valid ipv4 or ipv6 address
112 ip
= virtual_ip
.split('/')[0]
113 ipaddress
.ip_address(ip
)
114 if virtual_ip
and not ingress
:
115 raise NFSInvalidOperation('virtual_ip can only be provided with ingress enabled')
116 if not virtual_ip
and ingress
:
117 raise NFSInvalidOperation('ingress currently requires a virtual_ip')
118 invalid_str
= re
.search('[^A-Za-z0-9-_.]', cluster_id
)
120 raise NFSInvalidOperation(f
"cluster id {cluster_id} is invalid. "
121 f
"{invalid_str.group()} is char not permitted")
123 create_ganesha_pool(self
.mgr
)
125 self
.create_empty_rados_obj(cluster_id
)
127 if cluster_id
not in available_clusters(self
.mgr
):
128 self
._call
_orch
_apply
_nfs
(cluster_id
, placement
, virtual_ip
, port
)
129 return 0, "NFS Cluster Created Successfully", ""
130 return 0, "", f
"{cluster_id} cluster already exists"
131 except Exception as e
:
132 return exception_handler(e
, f
"NFS Cluster {cluster_id} could not be created")
134 def delete_nfs_cluster(self
, cluster_id
: str) -> Tuple
[int, str, str]:
136 cluster_list
= available_clusters(self
.mgr
)
137 if cluster_id
in cluster_list
:
138 self
.mgr
.export_mgr
.delete_all_exports(cluster_id
)
139 completion
= self
.mgr
.remove_service('ingress.nfs.' + cluster_id
)
140 orchestrator
.raise_if_exception(completion
)
141 completion
= self
.mgr
.remove_service('nfs.' + cluster_id
)
142 orchestrator
.raise_if_exception(completion
)
143 self
.delete_config_obj(cluster_id
)
144 return 0, "NFS Cluster Deleted Successfully", ""
145 return 0, "", "Cluster does not exist"
146 except Exception as e
:
147 return exception_handler(e
, f
"Failed to delete NFS Cluster {cluster_id}")
149 def list_nfs_cluster(self
) -> Tuple
[int, str, str]:
151 return 0, '\n'.join(available_clusters(self
.mgr
)), ""
152 except Exception as e
:
153 return exception_handler(e
, "Failed to list NFS Cluster")
155 def _show_nfs_cluster_info(self
, cluster_id
: str) -> Dict
[str, Any
]:
156 completion
= self
.mgr
.list_daemons(daemon_type
='nfs')
157 # Here completion.result is a list DaemonDescription objects
158 clusters
= orchestrator
.raise_if_exception(completion
)
159 backends
: List
[Dict
[str, Union
[Any
]]] = []
161 for cluster
in clusters
:
162 if cluster_id
== cluster
.service_id():
163 assert cluster
.hostname
168 c
= self
.mgr
.get_hosts()
169 orchestrator
.raise_if_exception(c
)
170 hosts
= [h
for h
in c
.result
or []
171 if h
.hostname
== cluster
.hostname
]
173 ip
= resolve_ip(hosts
[0].addr
)
176 ip
= resolve_ip(cluster
.hostname
)
178 "hostname": cluster
.hostname
,
180 "port": cluster
.ports
[0] if cluster
.ports
else None
182 except orchestrator
.OrchestratorError
:
185 r
: Dict
[str, Any
] = {
189 sc
= self
.mgr
.describe_service(service_type
='ingress')
190 services
= orchestrator
.raise_if_exception(sc
)
192 spec
= cast(IngressSpec
, i
.spec
)
193 if spec
.backend_service
== f
'nfs.{cluster_id}':
194 r
['virtual_ip'] = i
.virtual_ip
.split('/')[0] if i
.virtual_ip
else None
196 r
['port'] = i
.ports
[0]
198 r
['monitor_port'] = i
.ports
[1]
199 log
.debug("Successfully fetched %s info: %s", cluster_id
, r
)
202 def show_nfs_cluster_info(self
, cluster_id
: Optional
[str] = None) -> Tuple
[int, str, str]:
206 cluster_ls
= [cluster_id
]
208 cluster_ls
= available_clusters(self
.mgr
)
210 for cluster_id
in cluster_ls
:
211 res
= self
._show
_nfs
_cluster
_info
(cluster_id
)
213 info_res
[cluster_id
] = res
214 return (0, json
.dumps(info_res
, indent
=4), '')
215 except Exception as e
:
216 return exception_handler(e
, "Failed to show info for cluster")
218 def get_nfs_cluster_config(self
, cluster_id
: str) -> Tuple
[int, str, str]:
220 if cluster_id
in available_clusters(self
.mgr
):
221 rados_obj
= self
._rados
(cluster_id
)
222 conf
= rados_obj
.read_obj(user_conf_obj_name(cluster_id
))
223 return 0, conf
or "", ""
224 raise ClusterNotFound()
225 except Exception as e
:
226 return exception_handler(e
, f
"Fetching NFS-Ganesha Config failed for {cluster_id}")
228 def set_nfs_cluster_config(self
, cluster_id
: str, nfs_config
: str) -> Tuple
[int, str, str]:
230 if cluster_id
in available_clusters(self
.mgr
):
231 rados_obj
= self
._rados
(cluster_id
)
232 if rados_obj
.check_user_config():
233 return 0, "", "NFS-Ganesha User Config already exists"
234 rados_obj
.write_obj(nfs_config
, user_conf_obj_name(cluster_id
),
235 conf_obj_name(cluster_id
))
236 log
.debug("Successfully saved %s's user config: \n %s", cluster_id
, nfs_config
)
237 restart_nfs_service(self
.mgr
, cluster_id
)
238 return 0, "NFS-Ganesha Config Set Successfully", ""
239 raise ClusterNotFound()
240 except NotImplementedError:
241 return 0, "NFS-Ganesha Config Added Successfully "\
242 "(Manual Restart of NFS PODS required)", ""
243 except Exception as e
:
244 return exception_handler(e
, f
"Setting NFS-Ganesha Config failed for {cluster_id}")
246 def reset_nfs_cluster_config(self
, cluster_id
: str) -> Tuple
[int, str, str]:
248 if cluster_id
in available_clusters(self
.mgr
):
249 rados_obj
= self
._rados
(cluster_id
)
250 if not rados_obj
.check_user_config():
251 return 0, "", "NFS-Ganesha User Config does not exist"
252 rados_obj
.remove_obj(user_conf_obj_name(cluster_id
),
253 conf_obj_name(cluster_id
))
254 restart_nfs_service(self
.mgr
, cluster_id
)
255 return 0, "NFS-Ganesha Config Reset Successfully", ""
256 raise ClusterNotFound()
257 except NotImplementedError:
258 return 0, "NFS-Ganesha Config Removed Successfully "\
259 "(Manual Restart of NFS PODS required)", ""
260 except Exception as e
:
261 return exception_handler(e
, f
"Resetting NFS-Ganesha Config failed for {cluster_id}")
263 def _rados(self
, cluster_id
: str) -> NFSRados
:
264 """Return a new NFSRados object for the given cluster id."""
265 return NFSRados(self
.mgr
.rados
, cluster_id
)