# -*- coding: utf-8 -*-
from __future__ import absolute_import
+
import copy
-from typing import List
+from typing import List, Dict
+
+import cherrypy
from mgr_util import merge_dicts
from orchestrator import HostSpec
-from . import ApiController, RESTController, Task
+from . import ApiController, RESTController, Task, Endpoint, ReadPermission, \
+ UiApiController, BaseController
from .orchestrator import raise_if_no_orchestrator
from .. import mgr
from ..exceptions import DashboardException
def merge_hosts_by_hostname(ceph_hosts, orch_hosts):
# type: (List[dict], List[HostSpec]) -> List[dict]
- """Merge Ceph hosts with orchestrator hosts by hostnames.
+ """
+ Merge Ceph hosts with orchestrator hosts by hostnames.
:param ceph_hosts: hosts returned from mgr
:type ceph_hosts: list of dict
:return list of dict
"""
hosts = copy.deepcopy(ceph_hosts)
- orch_hosts_map = {
- host.hostname: {
- 'labels': host.labels
- }
- for host in orch_hosts
- }
-
- # Hosts in both Ceph and Orchestrator
+ orch_hosts_map = {host.hostname: host.to_json() for host in orch_hosts}
+
+ # Sort labels.
+ for hostname in orch_hosts_map:
+ orch_hosts_map[hostname]['labels'].sort()
+
+ # Hosts in both Ceph and Orchestrator.
for host in hosts:
hostname = host['hostname']
if hostname in orch_hosts_map:
- host['labels'] = orch_hosts_map[hostname]['labels']
+ host.update(orch_hosts_map[hostname])
host['sources']['orchestrator'] = True
orch_hosts_map.pop(hostname)
- # Hosts only in Orchestrator
+ # Hosts only in Orchestrator.
orch_hosts_only = [
- dict(hostname=hostname,
- ceph_version='',
- labels=orch_hosts_map[hostname]['labels'],
- services=[],
- sources={
- 'ceph': False,
- 'orchestrator': True
- }) for hostname in orch_hosts_map
+ merge_dicts(
+ {
+ 'ceph_version': '',
+ 'services': [],
+ 'sources': {
+ 'ceph': False,
+ 'orchestrator': True
+ }
+ }, orch_hosts_map[hostname]) for hostname in orch_hosts_map
]
hosts.extend(orch_hosts_only)
return hosts
ceph_hosts = []
if from_ceph:
ceph_hosts = [
- merge_dicts(server, {
- 'labels': [],
- 'sources': {
- 'ceph': True,
- 'orchestrator': False
- }
- }) for server in mgr.list_servers()
+ merge_dicts(
+ server, {
+ 'addr': '',
+ 'labels': [],
+ 'service_type': '',
+ 'sources': {
+ 'ceph': True,
+ 'orchestrator': False
+ },
+ 'status': ''
+ }) for server in mgr.list_servers()
]
if from_orchestrator:
orch = OrchClient.instance()
return ceph_hosts
+def get_host(hostname: str) -> Dict:
+ """
+ Get a specific host from Ceph or Orchestrator (if available).
+ :param hostname: The name of the host to fetch.
+ :raises: cherrypy.HTTPError: If host not found.
+ """
+ for host in get_hosts():
+ if host['hostname'] == hostname:
+ return host
+ raise cherrypy.HTTPError(404)
+
+
@ApiController('/host', Scope.HOSTS)
class Host(RESTController):
def list(self, sources=None):
@raise_if_no_orchestrator
@handle_orchestrator_error('host')
@host_task('create', {'hostname': '{hostname}'})
- def create(self, hostname):
+ def create(self, hostname): # pragma: no cover - requires realtime env
orch_client = OrchClient.instance()
self._check_orchestrator_host_op(orch_client, hostname, True)
orch_client.hosts.add(hostname)
@raise_if_no_orchestrator
@handle_orchestrator_error('host')
@host_task('delete', {'hostname': '{hostname}'})
- def delete(self, hostname):
+ def delete(self, hostname): # pragma: no cover - requires realtime env
orch_client = OrchClient.instance()
self._check_orchestrator_host_op(orch_client, hostname, False)
orch_client.hosts.remove(hostname)
- def _check_orchestrator_host_op(self, orch_client, hostname, add_host=True):
+ def _check_orchestrator_host_op(self, orch_client, hostname, add_host=True): # pragma:no cover
"""Check if we can adding or removing a host with orchestrator
:param orch_client: Orchestrator client
orch = OrchClient.instance()
daemons = orch.services.list_daemons(None, hostname)
return [d.to_json() for d in daemons]
+
+ @handle_orchestrator_error('host')
+ def get(self, hostname: str) -> Dict:
+ """
+ Get the specified host.
+ :raises: cherrypy.HTTPError: If host not found.
+ """
+ return get_host(hostname)
+
+ @raise_if_no_orchestrator
+ @handle_orchestrator_error('host')
+ def set(self, hostname: str, labels: List[str]):
+ """
+ Update the specified host.
+ Note, this is only supported when Ceph Orchestrator is enabled.
+ :param hostname: The name of the host to be processed.
+ :param labels: List of labels.
+ """
+ orch = OrchClient.instance()
+ host = get_host(hostname)
+ current_labels = set(host['labels'])
+ # Remove labels.
+ remove_labels = list(current_labels.difference(set(labels)))
+ for label in remove_labels:
+ orch.hosts.remove_label(hostname, label)
+ # Add labels.
+ add_labels = list(set(labels).difference(current_labels))
+ for label in add_labels:
+ orch.hosts.add_label(hostname, label)
+
+
+@UiApiController('/host', Scope.HOSTS)
+class HostUi(BaseController):
+ @Endpoint('GET')
+ @ReadPermission
+ @handle_orchestrator_error('host')
+ def labels(self) -> List[str]:
+ """
+ Get all host labels.
+ Note, host labels are only supported when Ceph Orchestrator is enabled.
+ If Ceph Orchestrator is not enabled, an empty list is returned.
+ :return: A list of all host labels.
+ """
+ labels = []
+ orch = OrchClient.instance()
+ if orch.available():
+ for host in orch.hosts.list():
+ labels.extend(host.labels)
+ labels.sort()
+ return list(set(labels)) # Filter duplicate labels.