]>
Commit | Line | Data |
---|---|---|
11fdf7f2 | 1 | # -*- coding: utf-8 -*- |
11fdf7f2 | 2 | |
f67539c2 | 3 | import logging |
adb31ebb | 4 | from functools import wraps |
39ae355f | 5 | from typing import Any, Dict, List, Optional, Tuple |
9f95a23c | 6 | |
adb31ebb | 7 | from ceph.deployment.service_spec import ServiceSpec |
f67539c2 TL |
8 | from orchestrator import DaemonDescription, DeviceLightLoc, HostSpec, \ |
9 | InventoryFilter, OrchestratorClientMixin, OrchestratorError, OrchResult, \ | |
10 | ServiceDescription, raise_if_exception | |
11 | ||
9f95a23c | 12 | from .. import mgr |
39ae355f | 13 | from ._paginate import ListPaginator |
9f95a23c TL |
14 | |
15 | logger = logging.getLogger('orchestrator') | |
11fdf7f2 TL |
16 | |
17 | ||
81eedcae | 18 | # pylint: disable=abstract-method |
9f95a23c | 19 | class 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 | ||
35 | def 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 | ||
44 | class ResourceManager(object): | |
45 | def __init__(self, api): | |
46 | self.api = api | |
47 | ||
48 | ||
49 | class 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 | |
91 | class 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 | 98 | class 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 | |
153 | class 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 |
167 | class 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 |
173 | class 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 |
203 | class 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 | ||
248 | class 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' |