]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/services/orchestrator.py
6829108e098d54315d9ba39eba891a6fcf12337c
[ceph.git] / ceph / src / pybind / mgr / dashboard / services / orchestrator.py
1 # -*- coding: utf-8 -*-
2
3 import logging
4 from functools import wraps
5 from typing import Any, Dict, List, Optional
6
7 from ceph.deployment.service_spec import ServiceSpec
8 from orchestrator import DaemonDescription, DeviceLightLoc, HostSpec, \
9 InventoryFilter, OrchestratorClientMixin, OrchestratorError, OrchResult, \
10 ServiceDescription, raise_if_exception
11
12 from .. import mgr
13
14 logger = logging.getLogger('orchestrator')
15
16
17 # pylint: disable=abstract-method
18 class OrchestratorAPI(OrchestratorClientMixin):
19 def __init__(self):
20 super(OrchestratorAPI, self).__init__()
21 self.set_mgr(mgr) # type: ignore
22
23 def status(self):
24 try:
25 status, message, _module_details = super().available()
26 logger.info("is orchestrator available: %s, %s", status, message)
27 return dict(available=status, message=message)
28 except (RuntimeError, OrchestratorError, ImportError) as e:
29 return dict(
30 available=False,
31 message='Orchestrator is unavailable: {}'.format(str(e)))
32
33
34 def wait_api_result(method):
35 @wraps(method)
36 def inner(self, *args, **kwargs):
37 completion = method(self, *args, **kwargs)
38 raise_if_exception(completion)
39 return completion.result
40 return inner
41
42
43 class ResourceManager(object):
44 def __init__(self, api):
45 self.api = api
46
47
48 class HostManger(ResourceManager):
49 @wait_api_result
50 def list(self) -> List[HostSpec]:
51 return self.api.get_hosts()
52
53 @wait_api_result
54 def enter_maintenance(self, hostname: str, force: bool = False):
55 return self.api.enter_host_maintenance(hostname, force)
56
57 @wait_api_result
58 def exit_maintenance(self, hostname: str):
59 return self.api.exit_host_maintenance(hostname)
60
61 def get(self, hostname: str) -> Optional[HostSpec]:
62 hosts = [host for host in self.list() if host.hostname == hostname]
63 return hosts[0] if hosts else None
64
65 @wait_api_result
66 def add(self, hostname: str, addr: str, labels: List[str]):
67 return self.api.add_host(HostSpec(hostname, addr=addr, labels=labels))
68
69 @wait_api_result
70 def get_facts(self, hostname: Optional[str] = None) -> List[Dict[str, Any]]:
71 return self.api.get_facts(hostname)
72
73 @wait_api_result
74 def remove(self, hostname: str):
75 return self.api.remove_host(hostname)
76
77 @wait_api_result
78 def add_label(self, host: str, label: str) -> OrchResult[str]:
79 return self.api.add_host_label(host, label)
80
81 @wait_api_result
82 def remove_label(self, host: str, label: str) -> OrchResult[str]:
83 return self.api.remove_host_label(host, label)
84
85 @wait_api_result
86 def drain(self, hostname: str):
87 return self.api.drain_host(hostname)
88
89
90 class InventoryManager(ResourceManager):
91 @wait_api_result
92 def list(self, hosts=None, refresh=False):
93 host_filter = InventoryFilter(hosts=hosts) if hosts else None
94 return self.api.get_inventory(host_filter=host_filter, refresh=refresh)
95
96
97 class ServiceManager(ResourceManager):
98 @wait_api_result
99 def list(self,
100 service_type: Optional[str] = None,
101 service_name: Optional[str] = None) -> List[ServiceDescription]:
102 return self.api.describe_service(service_type, service_name)
103
104 @wait_api_result
105 def get(self, service_name: str) -> ServiceDescription:
106 return self.api.describe_service(None, service_name)
107
108 @wait_api_result
109 def list_daemons(self,
110 service_name: Optional[str] = None,
111 daemon_type: Optional[str] = None,
112 hostname: Optional[str] = None) -> List[DaemonDescription]:
113 return self.api.list_daemons(service_name=service_name,
114 daemon_type=daemon_type,
115 host=hostname)
116
117 def reload(self, service_type, service_ids):
118 if not isinstance(service_ids, list):
119 service_ids = [service_ids]
120
121 completion_list = [
122 self.api.service_action('reload', service_type, service_name,
123 service_id)
124 for service_name, service_id in service_ids
125 ]
126 self.api.orchestrator_wait(completion_list)
127 for c in completion_list:
128 raise_if_exception(c)
129
130 @wait_api_result
131 def apply(self,
132 service_spec: Dict,
133 no_overwrite: Optional[bool] = False) -> OrchResult[List[str]]:
134 spec = ServiceSpec.from_json(service_spec)
135 return self.api.apply([spec], no_overwrite)
136
137 @wait_api_result
138 def remove(self, service_name: str) -> List[str]:
139 return self.api.remove_service(service_name)
140
141
142 class OsdManager(ResourceManager):
143 @wait_api_result
144 def create(self, drive_group_specs):
145 return self.api.apply_drivegroups(drive_group_specs)
146
147 @wait_api_result
148 def remove(self, osd_ids, replace=False, force=False):
149 return self.api.remove_osds(osd_ids, replace, force)
150
151 @wait_api_result
152 def removing_status(self):
153 return self.api.remove_osds_status()
154
155
156 class DaemonManager(ResourceManager):
157 @wait_api_result
158 def action(self, daemon_name='', action='', image=None):
159 return self.api.daemon_action(daemon_name=daemon_name, action=action, image=image)
160
161
162 class OrchClient(object):
163
164 _instance = None
165
166 @classmethod
167 def instance(cls):
168 # type: () -> OrchClient
169 if cls._instance is None:
170 cls._instance = cls()
171 return cls._instance
172
173 def __init__(self):
174 self.api = OrchestratorAPI()
175
176 self.hosts = HostManger(self.api)
177 self.inventory = InventoryManager(self.api)
178 self.services = ServiceManager(self.api)
179 self.osds = OsdManager(self.api)
180 self.daemons = DaemonManager(self.api)
181
182 def available(self, features: Optional[List[str]] = None) -> bool:
183 available = self.status()['available']
184 if available and features is not None:
185 return not self.get_missing_features(features)
186 return available
187
188 def status(self) -> Dict[str, Any]:
189 status = self.api.status()
190 status['features'] = {}
191 if status['available']:
192 status['features'] = self.api.get_feature_set()
193 return status
194
195 def get_missing_features(self, features: List[str]) -> List[str]:
196 supported_features = {k for k, v in self.api.get_feature_set().items() if v['available']}
197 return list(set(features) - supported_features)
198
199 @wait_api_result
200 def blink_device_light(self, hostname, device, ident_fault, on):
201 # type: (str, str, str, bool) -> OrchResult[List[str]]
202 return self.api.blink_device_light(
203 ident_fault, on, [DeviceLightLoc(hostname, device, device)])
204
205
206 class OrchFeature(object):
207 HOST_LIST = 'get_hosts'
208 HOST_ADD = 'add_host'
209 HOST_REMOVE = 'remove_host'
210 HOST_LABEL_ADD = 'add_host_label'
211 HOST_LABEL_REMOVE = 'remove_host_label'
212 HOST_MAINTENANCE_ENTER = 'enter_host_maintenance'
213 HOST_MAINTENANCE_EXIT = 'exit_host_maintenance'
214 HOST_DRAIN = 'drain_host'
215
216 SERVICE_LIST = 'describe_service'
217 SERVICE_CREATE = 'apply'
218 SERVICE_EDIT = 'apply'
219 SERVICE_DELETE = 'remove_service'
220 SERVICE_RELOAD = 'service_action'
221 DAEMON_LIST = 'list_daemons'
222
223 OSD_GET_REMOVE_STATUS = 'remove_osds_status'
224
225 OSD_CREATE = 'apply_drivegroups'
226 OSD_DELETE = 'remove_osds'
227
228 DEVICE_LIST = 'get_inventory'
229 DEVICE_BLINK_LIGHT = 'blink_device_light'
230
231 DAEMON_ACTION = 'daemon_action'