]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/controllers/host.py
import 15.2.5
[ceph.git] / ceph / src / pybind / mgr / dashboard / controllers / host.py
1 # -*- coding: utf-8 -*-
2 from __future__ import absolute_import
3
4 import copy
5
6 from typing import List, Dict
7
8 import cherrypy
9
10 from mgr_util import merge_dicts
11 from orchestrator import HostSpec
12 from . import ApiController, RESTController, Task, Endpoint, ReadPermission, \
13 UiApiController, BaseController
14 from .orchestrator import raise_if_no_orchestrator
15 from .. import mgr
16 from ..exceptions import DashboardException
17 from ..security import Scope
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]
29 """
30 Merge Ceph hosts with orchestrator hosts by hostnames.
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 """
38 hosts = copy.deepcopy(ceph_hosts)
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.
46 for host in hosts:
47 hostname = host['hostname']
48 if hostname in orch_hosts_map:
49 host.update(orch_hosts_map[hostname])
50 host['sources']['orchestrator'] = True
51 orch_hosts_map.pop(hostname)
52
53 # Hosts only in Orchestrator.
54 orch_hosts_only = [
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
64 ]
65 hosts.extend(orch_hosts_only)
66 return hosts
67
68
69 def get_hosts(from_ceph=True, from_orchestrator=True):
70 """
71 Get hosts from various sources.
72 """
73 ceph_hosts = []
74 if from_ceph:
75 ceph_hosts = [
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()
87 ]
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
93
94
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
107 @ApiController('/host', Scope.HOSTS)
108 class Host(RESTController):
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}'})
120 def create(self, hostname): # pragma: no cover - requires realtime env
121 orch_client = OrchClient.instance()
122 self._check_orchestrator_host_op(orch_client, hostname, True)
123 orch_client.hosts.add(hostname)
124
125 @raise_if_no_orchestrator
126 @handle_orchestrator_error('host')
127 @host_task('delete', {'hostname': '{hostname}'})
128 def delete(self, hostname): # pragma: no cover - requires realtime env
129 orch_client = OrchClient.instance()
130 self._check_orchestrator_host_op(orch_client, hostname, False)
131 orch_client.hosts.remove(hostname)
132
133 def _check_orchestrator_host_op(self, orch_client, hostname, add_host=True): # pragma:no cover
134 """Check if we can adding or removing a host with orchestrator
135
136 :param orch_client: Orchestrator client
137 :param add: True for adding host operation, False for removing host
138 :raise DashboardException
139 """
140 host = orch_client.hosts.get(hostname)
141 if add_host and host:
142 raise DashboardException(
143 code='orchestrator_add_existed_host',
144 msg='{} is already in orchestrator'.format(hostname),
145 component='orchestrator')
146 if not add_host and not host:
147 raise DashboardException(
148 code='orchestrator_remove_nonexistent_host',
149 msg='Remove a non-existent host {} from orchestrator'.format(
150 hostname),
151 component='orchestrator')
152
153 @RESTController.Resource('GET')
154 def devices(self, hostname):
155 # (str) -> List
156 return CephService.get_devices_by_host(hostname)
157
158 @RESTController.Resource('GET')
159 def smart(self, hostname):
160 # type: (str) -> dict
161 return CephService.get_smart_data_by_host(hostname)
162
163 @RESTController.Resource('GET')
164 @raise_if_no_orchestrator
165 def daemons(self, hostname: str) -> List[dict]:
166 orch = OrchClient.instance()
167 daemons = orch.services.list_daemons(None, hostname)
168 return [d.to_json() for d in daemons]
169
170 @handle_orchestrator_error('host')
171 def get(self, hostname: str) -> Dict:
172 """
173 Get the specified host.
174 :raises: cherrypy.HTTPError: If host not found.
175 """
176 return get_host(hostname)
177
178 @raise_if_no_orchestrator
179 @handle_orchestrator_error('host')
180 def set(self, hostname: str, labels: List[str]):
181 """
182 Update the specified host.
183 Note, this is only supported when Ceph Orchestrator is enabled.
184 :param hostname: The name of the host to be processed.
185 :param labels: List of labels.
186 """
187 orch = OrchClient.instance()
188 host = get_host(hostname)
189 current_labels = set(host['labels'])
190 # Remove labels.
191 remove_labels = list(current_labels.difference(set(labels)))
192 for label in remove_labels:
193 orch.hosts.remove_label(hostname, label)
194 # Add labels.
195 add_labels = list(set(labels).difference(current_labels))
196 for label in add_labels:
197 orch.hosts.add_label(hostname, label)
198
199
200 @UiApiController('/host', Scope.HOSTS)
201 class HostUi(BaseController):
202 @Endpoint('GET')
203 @ReadPermission
204 @handle_orchestrator_error('host')
205 def labels(self) -> List[str]:
206 """
207 Get all host labels.
208 Note, host labels are only supported when Ceph Orchestrator is enabled.
209 If Ceph Orchestrator is not enabled, an empty list is returned.
210 :return: A list of all host labels.
211 """
212 labels = []
213 orch = OrchClient.instance()
214 if orch.available():
215 for host in orch.hosts.list():
216 labels.extend(host.labels)
217 labels.sort()
218 return list(set(labels)) # Filter duplicate labels.