]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | # -*- coding: utf-8 -*- |
2 | from __future__ import absolute_import | |
f6b5b4d7 | 3 | |
9f95a23c | 4 | import copy |
11fdf7f2 | 5 | |
f6b5b4d7 TL |
6 | from typing import List, Dict |
7 | ||
8 | import cherrypy | |
9f95a23c TL |
9 | |
10 | from mgr_util import merge_dicts | |
11 | from orchestrator import HostSpec | |
f6b5b4d7 | 12 | from . import ApiController, RESTController, Task, Endpoint, ReadPermission, \ |
f91f0fd5 | 13 | UiApiController, BaseController, allow_empty_body |
9f95a23c | 14 | from .orchestrator import raise_if_no_orchestrator |
11fdf7f2 | 15 | from .. import mgr |
9f95a23c | 16 | from ..exceptions import DashboardException |
11fdf7f2 | 17 | from ..security import Scope |
9f95a23c TL |
18 | from ..services.orchestrator import OrchClient |
19 | from ..services.ceph_service import CephService | |
20 | from ..services.exception import handle_orchestrator_error | |
21 | ||
22 | ||
23 | def host_task(name, metadata, wait_for=10.0): | |
24 | return Task("host/{}".format(name), metadata, wait_for) | |
25 | ||
26 | ||
27 | def merge_hosts_by_hostname(ceph_hosts, orch_hosts): | |
28 | # type: (List[dict], List[HostSpec]) -> List[dict] | |
f6b5b4d7 TL |
29 | """ |
30 | Merge Ceph hosts with orchestrator hosts by hostnames. | |
9f95a23c TL |
31 | |
32 | :param ceph_hosts: hosts returned from mgr | |
33 | :type ceph_hosts: list of dict | |
34 | :param orch_hosts: hosts returned from ochestrator | |
35 | :type orch_hosts: list of HostSpec | |
36 | :return list of dict | |
37 | """ | |
e306af50 | 38 | hosts = copy.deepcopy(ceph_hosts) |
f6b5b4d7 TL |
39 | orch_hosts_map = {host.hostname: host.to_json() for host in orch_hosts} |
40 | ||
41 | # Sort labels. | |
42 | for hostname in orch_hosts_map: | |
43 | orch_hosts_map[hostname]['labels'].sort() | |
44 | ||
45 | # Hosts in both Ceph and Orchestrator. | |
e306af50 TL |
46 | for host in hosts: |
47 | hostname = host['hostname'] | |
48 | if hostname in orch_hosts_map: | |
f6b5b4d7 | 49 | host.update(orch_hosts_map[hostname]) |
e306af50 TL |
50 | host['sources']['orchestrator'] = True |
51 | orch_hosts_map.pop(hostname) | |
9f95a23c | 52 | |
f6b5b4d7 | 53 | # Hosts only in Orchestrator. |
e306af50 | 54 | orch_hosts_only = [ |
f6b5b4d7 TL |
55 | merge_dicts( |
56 | { | |
57 | 'ceph_version': '', | |
58 | 'services': [], | |
59 | 'sources': { | |
60 | 'ceph': False, | |
61 | 'orchestrator': True | |
62 | } | |
63 | }, orch_hosts_map[hostname]) for hostname in orch_hosts_map | |
e306af50 TL |
64 | ] |
65 | hosts.extend(orch_hosts_only) | |
66 | return hosts | |
9f95a23c TL |
67 | |
68 | ||
69 | def get_hosts(from_ceph=True, from_orchestrator=True): | |
e306af50 TL |
70 | """ |
71 | Get hosts from various sources. | |
72 | """ | |
9f95a23c TL |
73 | ceph_hosts = [] |
74 | if from_ceph: | |
e306af50 | 75 | ceph_hosts = [ |
f6b5b4d7 TL |
76 | merge_dicts( |
77 | server, { | |
78 | 'addr': '', | |
79 | 'labels': [], | |
80 | 'service_type': '', | |
81 | 'sources': { | |
82 | 'ceph': True, | |
83 | 'orchestrator': False | |
84 | }, | |
85 | 'status': '' | |
86 | }) for server in mgr.list_servers() | |
e306af50 | 87 | ] |
9f95a23c TL |
88 | if from_orchestrator: |
89 | orch = OrchClient.instance() | |
90 | if orch.available(): | |
91 | return merge_hosts_by_hostname(ceph_hosts, orch.hosts.list()) | |
92 | return ceph_hosts | |
11fdf7f2 TL |
93 | |
94 | ||
f6b5b4d7 TL |
95 | def get_host(hostname: str) -> Dict: |
96 | """ | |
97 | Get a specific host from Ceph or Orchestrator (if available). | |
98 | :param hostname: The name of the host to fetch. | |
99 | :raises: cherrypy.HTTPError: If host not found. | |
100 | """ | |
101 | for host in get_hosts(): | |
102 | if host['hostname'] == hostname: | |
103 | return host | |
104 | raise cherrypy.HTTPError(404) | |
105 | ||
106 | ||
11fdf7f2 TL |
107 | @ApiController('/host', Scope.HOSTS) |
108 | class Host(RESTController): | |
9f95a23c TL |
109 | def list(self, sources=None): |
110 | if sources is None: | |
111 | return get_hosts() | |
112 | _sources = sources.split(',') | |
113 | from_ceph = 'ceph' in _sources | |
114 | from_orchestrator = 'orchestrator' in _sources | |
115 | return get_hosts(from_ceph, from_orchestrator) | |
116 | ||
117 | @raise_if_no_orchestrator | |
118 | @handle_orchestrator_error('host') | |
119 | @host_task('create', {'hostname': '{hostname}'}) | |
f6b5b4d7 | 120 | def create(self, hostname): # pragma: no cover - requires realtime env |
9f95a23c TL |
121 | orch_client = OrchClient.instance() |
122 | self._check_orchestrator_host_op(orch_client, hostname, True) | |
123 | orch_client.hosts.add(hostname) | |
f91f0fd5 | 124 | create._cp_config = {'tools.json_in.force': False} # pylint: disable=W0212 |
9f95a23c TL |
125 | |
126 | @raise_if_no_orchestrator | |
127 | @handle_orchestrator_error('host') | |
128 | @host_task('delete', {'hostname': '{hostname}'}) | |
f91f0fd5 | 129 | @allow_empty_body |
f6b5b4d7 | 130 | def delete(self, hostname): # pragma: no cover - requires realtime env |
9f95a23c TL |
131 | orch_client = OrchClient.instance() |
132 | self._check_orchestrator_host_op(orch_client, hostname, False) | |
133 | orch_client.hosts.remove(hostname) | |
134 | ||
f6b5b4d7 | 135 | def _check_orchestrator_host_op(self, orch_client, hostname, add_host=True): # pragma:no cover |
9f95a23c TL |
136 | """Check if we can adding or removing a host with orchestrator |
137 | ||
138 | :param orch_client: Orchestrator client | |
139 | :param add: True for adding host operation, False for removing host | |
140 | :raise DashboardException | |
141 | """ | |
142 | host = orch_client.hosts.get(hostname) | |
143 | if add_host and host: | |
144 | raise DashboardException( | |
145 | code='orchestrator_add_existed_host', | |
146 | msg='{} is already in orchestrator'.format(hostname), | |
147 | component='orchestrator') | |
148 | if not add_host and not host: | |
149 | raise DashboardException( | |
150 | code='orchestrator_remove_nonexistent_host', | |
151 | msg='Remove a non-existent host {} from orchestrator'.format( | |
152 | hostname), | |
153 | component='orchestrator') | |
154 | ||
155 | @RESTController.Resource('GET') | |
156 | def devices(self, hostname): | |
157 | # (str) -> List | |
158 | return CephService.get_devices_by_host(hostname) | |
159 | ||
160 | @RESTController.Resource('GET') | |
161 | def smart(self, hostname): | |
162 | # type: (str) -> dict | |
163 | return CephService.get_smart_data_by_host(hostname) | |
164 | ||
165 | @RESTController.Resource('GET') | |
166 | @raise_if_no_orchestrator | |
167 | def daemons(self, hostname: str) -> List[dict]: | |
168 | orch = OrchClient.instance() | |
f91f0fd5 | 169 | daemons = orch.services.list_daemons(hostname=hostname) |
9f95a23c | 170 | return [d.to_json() for d in daemons] |
f6b5b4d7 TL |
171 | |
172 | @handle_orchestrator_error('host') | |
173 | def get(self, hostname: str) -> Dict: | |
174 | """ | |
175 | Get the specified host. | |
176 | :raises: cherrypy.HTTPError: If host not found. | |
177 | """ | |
178 | return get_host(hostname) | |
179 | ||
180 | @raise_if_no_orchestrator | |
181 | @handle_orchestrator_error('host') | |
182 | def set(self, hostname: str, labels: List[str]): | |
183 | """ | |
184 | Update the specified host. | |
185 | Note, this is only supported when Ceph Orchestrator is enabled. | |
186 | :param hostname: The name of the host to be processed. | |
187 | :param labels: List of labels. | |
188 | """ | |
189 | orch = OrchClient.instance() | |
190 | host = get_host(hostname) | |
191 | current_labels = set(host['labels']) | |
192 | # Remove labels. | |
193 | remove_labels = list(current_labels.difference(set(labels))) | |
194 | for label in remove_labels: | |
195 | orch.hosts.remove_label(hostname, label) | |
196 | # Add labels. | |
197 | add_labels = list(set(labels).difference(current_labels)) | |
198 | for label in add_labels: | |
199 | orch.hosts.add_label(hostname, label) | |
200 | ||
201 | ||
202 | @UiApiController('/host', Scope.HOSTS) | |
203 | class HostUi(BaseController): | |
204 | @Endpoint('GET') | |
205 | @ReadPermission | |
206 | @handle_orchestrator_error('host') | |
207 | def labels(self) -> List[str]: | |
208 | """ | |
209 | Get all host labels. | |
210 | Note, host labels are only supported when Ceph Orchestrator is enabled. | |
211 | If Ceph Orchestrator is not enabled, an empty list is returned. | |
212 | :return: A list of all host labels. | |
213 | """ | |
214 | labels = [] | |
215 | orch = OrchClient.instance() | |
216 | if orch.available(): | |
217 | for host in orch.hosts.list(): | |
218 | labels.extend(host.labels) | |
219 | labels.sort() | |
220 | return list(set(labels)) # Filter duplicate labels. |