]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/services/orchestrator.py
update ceph source to reef 18.2.1
[ceph.git] / ceph / src / pybind / mgr / dashboard / services / orchestrator.py
CommitLineData
11fdf7f2 1# -*- coding: utf-8 -*-
11fdf7f2 2
f67539c2 3import logging
adb31ebb 4from functools import wraps
39ae355f 5from typing import Any, Dict, List, Optional, Tuple
9f95a23c 6
adb31ebb 7from ceph.deployment.service_spec import ServiceSpec
f67539c2
TL
8from orchestrator import DaemonDescription, DeviceLightLoc, HostSpec, \
9 InventoryFilter, OrchestratorClientMixin, OrchestratorError, OrchResult, \
10 ServiceDescription, raise_if_exception
11
9f95a23c 12from .. import mgr
39ae355f 13from ._paginate import ListPaginator
9f95a23c
TL
14
15logger = logging.getLogger('orchestrator')
11fdf7f2
TL
16
17
81eedcae 18# pylint: disable=abstract-method
9f95a23c 19class OrchestratorAPI(OrchestratorClientMixin):
81eedcae 20 def __init__(self):
9f95a23c
TL
21 super(OrchestratorAPI, self).__init__()
22 self.set_mgr(mgr) # type: ignore
23
24 def status(self):
25 try:
f67539c2
TL
26 status, message, _module_details = super().available()
27 logger.info("is orchestrator available: %s, %s", status, message)
28 return dict(available=status, message=message)
29 except (RuntimeError, OrchestratorError, ImportError) as e:
9f95a23c
TL
30 return dict(
31 available=False,
f67539c2 32 message='Orchestrator is unavailable: {}'.format(str(e)))
9f95a23c
TL
33
34
35def wait_api_result(method):
36 @wraps(method)
37 def inner(self, *args, **kwargs):
38 completion = method(self, *args, **kwargs)
81eedcae 39 raise_if_exception(completion)
11fdf7f2 40 return completion.result
9f95a23c
TL
41 return inner
42
43
44class ResourceManager(object):
45 def __init__(self, api):
46 self.api = api
47
48
49class HostManger(ResourceManager):
50 @wait_api_result
51 def list(self) -> List[HostSpec]:
52 return self.api.get_hosts()
53
f67539c2
TL
54 @wait_api_result
55 def enter_maintenance(self, hostname: str, force: bool = False):
56 return self.api.enter_host_maintenance(hostname, force)
57
58 @wait_api_result
59 def exit_maintenance(self, hostname: str):
60 return self.api.exit_host_maintenance(hostname)
61
9f95a23c
TL
62 def get(self, hostname: str) -> Optional[HostSpec]:
63 hosts = [host for host in self.list() if host.hostname == hostname]
64 return hosts[0] if hosts else None
65
66 @wait_api_result
b3b6e05e
TL
67 def add(self, hostname: str, addr: str, labels: List[str]):
68 return self.api.add_host(HostSpec(hostname, addr=addr, labels=labels))
9f95a23c 69
a4b75251
TL
70 @wait_api_result
71 def get_facts(self, hostname: Optional[str] = None) -> List[Dict[str, Any]]:
72 return self.api.get_facts(hostname)
73
9f95a23c
TL
74 @wait_api_result
75 def remove(self, hostname: str):
76 return self.api.remove_host(hostname)
77
f6b5b4d7 78 @wait_api_result
f67539c2 79 def add_label(self, host: str, label: str) -> OrchResult[str]:
f6b5b4d7
TL
80 return self.api.add_host_label(host, label)
81
82 @wait_api_result
f67539c2 83 def remove_label(self, host: str, label: str) -> OrchResult[str]:
f6b5b4d7
TL
84 return self.api.remove_host_label(host, label)
85
20effc67
TL
86 @wait_api_result
87 def drain(self, hostname: str):
88 return self.api.drain_host(hostname)
89
9f95a23c
TL
90
91class InventoryManager(ResourceManager):
92 @wait_api_result
93 def list(self, hosts=None, refresh=False):
94 host_filter = InventoryFilter(hosts=hosts) if hosts else None
95 return self.api.get_inventory(host_filter=host_filter, refresh=refresh)
11fdf7f2 96
11fdf7f2 97
9f95a23c 98class ServiceManager(ResourceManager):
f91f0fd5
TL
99 def list(self,
100 service_type: Optional[str] = None,
39ae355f
TL
101 service_name: Optional[str] = None,
102 offset: int = 0, limit: int = -1,
103 sort: str = '+service_name', search: str = '') -> Tuple[List[Dict[Any, Any]], int]:
104 services = self.api.describe_service(service_type, service_name)
105 services = [service.to_dict() for service in services.result]
106 paginator = ListPaginator(offset, limit, sort, search,
107 input_list=services,
108 searchable_params=['service_name', 'status.running',
109 'status.last_refreshed', 'status.size'],
110 sortable_params=['service_name', 'status.running',
111 'status.last_refreshed', 'status.size'],
112 default_sort='+service_name')
113 return list(paginator.list()), paginator.get_count()
9f95a23c
TL
114
115 @wait_api_result
116 def get(self, service_name: str) -> ServiceDescription:
117 return self.api.describe_service(None, service_name)
118
119 @wait_api_result
120 def list_daemons(self,
121 service_name: Optional[str] = None,
f91f0fd5 122 daemon_type: Optional[str] = None,
9f95a23c 123 hostname: Optional[str] = None) -> List[DaemonDescription]:
f91f0fd5
TL
124 return self.api.list_daemons(service_name=service_name,
125 daemon_type=daemon_type,
126 host=hostname)
9f95a23c
TL
127
128 def reload(self, service_type, service_ids):
11fdf7f2
TL
129 if not isinstance(service_ids, list):
130 service_ids = [service_ids]
131
9f95a23c
TL
132 completion_list = [
133 self.api.service_action('reload', service_type, service_name,
134 service_id)
135 for service_name, service_id in service_ids
136 ]
137 self.api.orchestrator_wait(completion_list)
81eedcae
TL
138 for c in completion_list:
139 raise_if_exception(c)
9f95a23c 140
adb31ebb 141 @wait_api_result
2a845540
TL
142 def apply(self,
143 service_spec: Dict,
144 no_overwrite: Optional[bool] = False) -> OrchResult[List[str]]:
adb31ebb 145 spec = ServiceSpec.from_json(service_spec)
2a845540 146 return self.api.apply([spec], no_overwrite)
adb31ebb
TL
147
148 @wait_api_result
149 def remove(self, service_name: str) -> List[str]:
150 return self.api.remove_service(service_name)
151
9f95a23c
TL
152
153class OsdManager(ResourceManager):
154 @wait_api_result
155 def create(self, drive_group_specs):
156 return self.api.apply_drivegroups(drive_group_specs)
157
158 @wait_api_result
f6b5b4d7
TL
159 def remove(self, osd_ids, replace=False, force=False):
160 return self.api.remove_osds(osd_ids, replace, force)
9f95a23c
TL
161
162 @wait_api_result
163 def removing_status(self):
164 return self.api.remove_osds_status()
165
166
20effc67
TL
167class DaemonManager(ResourceManager):
168 @wait_api_result
169 def action(self, daemon_name='', action='', image=None):
170 return self.api.daemon_action(daemon_name=daemon_name, action=action, image=image)
171
172
aee94f69
TL
173class UpgradeManager(ResourceManager):
174 @wait_api_result
175 def list(self, image: Optional[str], tags: bool,
176 show_all_versions: Optional[bool]) -> Dict[Any, Any]:
177 return self.api.upgrade_ls(image, tags, show_all_versions)
178
179 @wait_api_result
180 def status(self):
181 return self.api.upgrade_status()
182
183 @wait_api_result
184 def start(self, image: str, version: str, daemon_types: Optional[List[str]] = None,
185 host_placement: Optional[str] = None, services: Optional[List[str]] = None,
186 limit: Optional[int] = None) -> str:
187 return self.api.upgrade_start(image, version, daemon_types, host_placement, services,
188 limit)
189
190 @wait_api_result
191 def pause(self) -> str:
192 return self.api.upgrade_pause()
193
194 @wait_api_result
195 def resume(self) -> str:
196 return self.api.upgrade_resume()
197
198 @wait_api_result
199 def stop(self) -> str:
200 return self.api.upgrade_stop()
201
202
9f95a23c
TL
203class OrchClient(object):
204
205 _instance = None
206
207 @classmethod
208 def instance(cls):
f67539c2 209 # type: () -> OrchClient
9f95a23c
TL
210 if cls._instance is None:
211 cls._instance = cls()
212 return cls._instance
213
214 def __init__(self):
215 self.api = OrchestratorAPI()
216
217 self.hosts = HostManger(self.api)
218 self.inventory = InventoryManager(self.api)
219 self.services = ServiceManager(self.api)
220 self.osds = OsdManager(self.api)
20effc67 221 self.daemons = DaemonManager(self.api)
aee94f69 222 self.upgrades = UpgradeManager(self.api)
9f95a23c 223
f67539c2
TL
224 def available(self, features: Optional[List[str]] = None) -> bool:
225 available = self.status()['available']
226 if available and features is not None:
227 return not self.get_missing_features(features)
228 return available
9f95a23c 229
f67539c2
TL
230 def status(self) -> Dict[str, Any]:
231 status = self.api.status()
232 status['features'] = {}
233 if status['available']:
234 status['features'] = self.api.get_feature_set()
235 return status
236
237 def get_missing_features(self, features: List[str]) -> List[str]:
238 supported_features = {k for k, v in self.api.get_feature_set().items() if v['available']}
239 return list(set(features) - supported_features)
9f95a23c
TL
240
241 @wait_api_result
242 def blink_device_light(self, hostname, device, ident_fault, on):
f67539c2 243 # type: (str, str, str, bool) -> OrchResult[List[str]]
9f95a23c
TL
244 return self.api.blink_device_light(
245 ident_fault, on, [DeviceLightLoc(hostname, device, device)])
f67539c2
TL
246
247
248class OrchFeature(object):
249 HOST_LIST = 'get_hosts'
a4b75251
TL
250 HOST_ADD = 'add_host'
251 HOST_REMOVE = 'remove_host'
f67539c2
TL
252 HOST_LABEL_ADD = 'add_host_label'
253 HOST_LABEL_REMOVE = 'remove_host_label'
254 HOST_MAINTENANCE_ENTER = 'enter_host_maintenance'
255 HOST_MAINTENANCE_EXIT = 'exit_host_maintenance'
20effc67 256 HOST_DRAIN = 'drain_host'
f67539c2
TL
257
258 SERVICE_LIST = 'describe_service'
259 SERVICE_CREATE = 'apply'
a4b75251 260 SERVICE_EDIT = 'apply'
f67539c2
TL
261 SERVICE_DELETE = 'remove_service'
262 SERVICE_RELOAD = 'service_action'
263 DAEMON_LIST = 'list_daemons'
264
265 OSD_GET_REMOVE_STATUS = 'remove_osds_status'
266
267 OSD_CREATE = 'apply_drivegroups'
268 OSD_DELETE = 'remove_osds'
269
270 DEVICE_LIST = 'get_inventory'
271 DEVICE_BLINK_LIGHT = 'blink_device_light'
20effc67
TL
272
273 DAEMON_ACTION = 'daemon_action'
aee94f69
TL
274
275 UPGRADE_LIST = 'upgrade_ls'
276 UPGRADE_STATUS = 'upgrade_status'
277 UPGRADE_START = 'upgrade_start'
278 UPGRADE_PAUSE = 'upgrade_pause'
279 UPGRADE_RESUME = 'upgrade_resume'
280 UPGRADE_STOP = 'upgrade_stop'