]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/controllers/host.py
import 15.2.9
[ceph.git] / ceph / src / pybind / mgr / dashboard / controllers / host.py
CommitLineData
11fdf7f2
TL
1# -*- coding: utf-8 -*-
2from __future__ import absolute_import
f6b5b4d7 3
9f95a23c 4import copy
11fdf7f2 5
f6b5b4d7
TL
6from typing import List, Dict
7
8import cherrypy
9f95a23c
TL
9
10from mgr_util import merge_dicts
11from orchestrator import HostSpec
f6b5b4d7 12from . import ApiController, RESTController, Task, Endpoint, ReadPermission, \
f91f0fd5 13 UiApiController, BaseController, allow_empty_body
9f95a23c 14from .orchestrator import raise_if_no_orchestrator
11fdf7f2 15from .. import mgr
9f95a23c 16from ..exceptions import DashboardException
11fdf7f2 17from ..security import Scope
9f95a23c
TL
18from ..services.orchestrator import OrchClient
19from ..services.ceph_service import CephService
20from ..services.exception import handle_orchestrator_error
21
22
23def host_task(name, metadata, wait_for=10.0):
24 return Task("host/{}".format(name), metadata, wait_for)
25
26
27def 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
69def 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
95def 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)
108class 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)
203class 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.