]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/services/orchestrator.py
import quincy beta 17.1.0
[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
f67539c2 5from typing import Any, Dict, List, Optional
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
9f95a23c
TL
13
14logger = logging.getLogger('orchestrator')
11fdf7f2
TL
15
16
81eedcae 17# pylint: disable=abstract-method
9f95a23c 18class OrchestratorAPI(OrchestratorClientMixin):
81eedcae 19 def __init__(self):
9f95a23c
TL
20 super(OrchestratorAPI, self).__init__()
21 self.set_mgr(mgr) # type: ignore
22
23 def status(self):
24 try:
f67539c2
TL
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:
9f95a23c
TL
29 return dict(
30 available=False,
f67539c2 31 message='Orchestrator is unavailable: {}'.format(str(e)))
9f95a23c
TL
32
33
34def wait_api_result(method):
35 @wraps(method)
36 def inner(self, *args, **kwargs):
37 completion = method(self, *args, **kwargs)
81eedcae 38 raise_if_exception(completion)
11fdf7f2 39 return completion.result
9f95a23c
TL
40 return inner
41
42
43class ResourceManager(object):
44 def __init__(self, api):
45 self.api = api
46
47
48class HostManger(ResourceManager):
49 @wait_api_result
50 def list(self) -> List[HostSpec]:
51 return self.api.get_hosts()
52
f67539c2
TL
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
9f95a23c
TL
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
b3b6e05e
TL
66 def add(self, hostname: str, addr: str, labels: List[str]):
67 return self.api.add_host(HostSpec(hostname, addr=addr, labels=labels))
9f95a23c 68
a4b75251
TL
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
9f95a23c
TL
73 @wait_api_result
74 def remove(self, hostname: str):
75 return self.api.remove_host(hostname)
76
f6b5b4d7 77 @wait_api_result
f67539c2 78 def add_label(self, host: str, label: str) -> OrchResult[str]:
f6b5b4d7
TL
79 return self.api.add_host_label(host, label)
80
81 @wait_api_result
f67539c2 82 def remove_label(self, host: str, label: str) -> OrchResult[str]:
f6b5b4d7
TL
83 return self.api.remove_host_label(host, label)
84
20effc67
TL
85 @wait_api_result
86 def drain(self, hostname: str):
87 return self.api.drain_host(hostname)
88
9f95a23c
TL
89
90class 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)
11fdf7f2 95
11fdf7f2 96
9f95a23c
TL
97class ServiceManager(ResourceManager):
98 @wait_api_result
f91f0fd5
TL
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)
9f95a23c
TL
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,
f91f0fd5 111 daemon_type: Optional[str] = None,
9f95a23c 112 hostname: Optional[str] = None) -> List[DaemonDescription]:
f91f0fd5
TL
113 return self.api.list_daemons(service_name=service_name,
114 daemon_type=daemon_type,
115 host=hostname)
9f95a23c
TL
116
117 def reload(self, service_type, service_ids):
11fdf7f2
TL
118 if not isinstance(service_ids, list):
119 service_ids = [service_ids]
120
9f95a23c
TL
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)
81eedcae
TL
127 for c in completion_list:
128 raise_if_exception(c)
9f95a23c 129
adb31ebb 130 @wait_api_result
f67539c2 131 def apply(self, service_spec: Dict) -> OrchResult[List[str]]:
adb31ebb
TL
132 spec = ServiceSpec.from_json(service_spec)
133 return self.api.apply([spec])
134
135 @wait_api_result
136 def remove(self, service_name: str) -> List[str]:
137 return self.api.remove_service(service_name)
138
9f95a23c
TL
139
140class OsdManager(ResourceManager):
141 @wait_api_result
142 def create(self, drive_group_specs):
143 return self.api.apply_drivegroups(drive_group_specs)
144
145 @wait_api_result
f6b5b4d7
TL
146 def remove(self, osd_ids, replace=False, force=False):
147 return self.api.remove_osds(osd_ids, replace, force)
9f95a23c
TL
148
149 @wait_api_result
150 def removing_status(self):
151 return self.api.remove_osds_status()
152
153
20effc67
TL
154class DaemonManager(ResourceManager):
155 @wait_api_result
156 def action(self, daemon_name='', action='', image=None):
157 return self.api.daemon_action(daemon_name=daemon_name, action=action, image=image)
158
159
9f95a23c
TL
160class OrchClient(object):
161
162 _instance = None
163
164 @classmethod
165 def instance(cls):
f67539c2 166 # type: () -> OrchClient
9f95a23c
TL
167 if cls._instance is None:
168 cls._instance = cls()
169 return cls._instance
170
171 def __init__(self):
172 self.api = OrchestratorAPI()
173
174 self.hosts = HostManger(self.api)
175 self.inventory = InventoryManager(self.api)
176 self.services = ServiceManager(self.api)
177 self.osds = OsdManager(self.api)
20effc67 178 self.daemons = DaemonManager(self.api)
9f95a23c 179
f67539c2
TL
180 def available(self, features: Optional[List[str]] = None) -> bool:
181 available = self.status()['available']
182 if available and features is not None:
183 return not self.get_missing_features(features)
184 return available
9f95a23c 185
f67539c2
TL
186 def status(self) -> Dict[str, Any]:
187 status = self.api.status()
188 status['features'] = {}
189 if status['available']:
190 status['features'] = self.api.get_feature_set()
191 return status
192
193 def get_missing_features(self, features: List[str]) -> List[str]:
194 supported_features = {k for k, v in self.api.get_feature_set().items() if v['available']}
195 return list(set(features) - supported_features)
9f95a23c
TL
196
197 @wait_api_result
198 def blink_device_light(self, hostname, device, ident_fault, on):
f67539c2 199 # type: (str, str, str, bool) -> OrchResult[List[str]]
9f95a23c
TL
200 return self.api.blink_device_light(
201 ident_fault, on, [DeviceLightLoc(hostname, device, device)])
f67539c2
TL
202
203
204class OrchFeature(object):
205 HOST_LIST = 'get_hosts'
a4b75251
TL
206 HOST_ADD = 'add_host'
207 HOST_REMOVE = 'remove_host'
f67539c2
TL
208 HOST_LABEL_ADD = 'add_host_label'
209 HOST_LABEL_REMOVE = 'remove_host_label'
210 HOST_MAINTENANCE_ENTER = 'enter_host_maintenance'
211 HOST_MAINTENANCE_EXIT = 'exit_host_maintenance'
20effc67 212 HOST_DRAIN = 'drain_host'
f67539c2
TL
213
214 SERVICE_LIST = 'describe_service'
215 SERVICE_CREATE = 'apply'
a4b75251 216 SERVICE_EDIT = 'apply'
f67539c2
TL
217 SERVICE_DELETE = 'remove_service'
218 SERVICE_RELOAD = 'service_action'
219 DAEMON_LIST = 'list_daemons'
220
221 OSD_GET_REMOVE_STATUS = 'remove_osds_status'
222
223 OSD_CREATE = 'apply_drivegroups'
224 OSD_DELETE = 'remove_osds'
225
226 DEVICE_LIST = 'get_inventory'
227 DEVICE_BLINK_LIGHT = 'blink_device_light'
20effc67
TL
228
229 DAEMON_ACTION = 'daemon_action'