]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/pybind/mgr/dashboard/controllers/host.py
import 15.2.5
[ceph.git] / ceph / src / pybind / mgr / dashboard / controllers / host.py
index 5995161bee1098d5e5eb4e929203b167db2ed636..db498c8aaad8983eeae721bb5a5afb97ce997a5d 100644 (file)
@@ -1,12 +1,16 @@
 # -*- 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
@@ -22,7 +26,8 @@ def host_task(name, metadata, wait_for=10.0):
 
 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
@@ -31,31 +36,31 @@ def merge_hosts_by_hostname(ceph_hosts, orch_hosts):
     :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
@@ -68,13 +73,17 @@ def get_hosts(from_ceph=True, from_orchestrator=True):
     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()
@@ -83,6 +92,18 @@ def get_hosts(from_ceph=True, from_orchestrator=True):
     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):
@@ -96,7 +117,7 @@ class Host(RESTController):
     @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)
@@ -104,12 +125,12 @@ class Host(RESTController):
     @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
@@ -145,3 +166,53 @@ class Host(RESTController):
         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.