From 320bbeec1129d56e7501e1df501af91fd55d4fe5 Mon Sep 17 00:00:00 2001 From: Kiefer Chang Date: Thu, 16 Jan 2020 17:44:27 +0800 Subject: [PATCH] mgr/dashboard: support multiple DriveGroups when creating OSDs The `create_osds` call in orchestrator uses multiple named DriveGroups as the parameter. Adapt the change in Dashboard. Some minor polishes: - Use task manager to wrap the operation. - The submit button in Preview modal is changed from `Add` to `Create`. - POST `/api/osd` to create OSDs: - Bare OSDs for OSD service container { "method": "bare", "data": { "uuid": "xxxx", "svc_id": 5 } } - OSDs with devices (DriveGroups) { "method": "drive_groups", "data": { < drive group spec here> } } - `/orchestrator/osd` endpoint is removed. Fixes: https://tracker.ceph.com/issues/43615 Signed-off-by: Kiefer Chang --- qa/tasks/mgr/dashboard/test_orchestrator.py | 53 ------------ qa/tasks/mgr/dashboard/test_osd.py | 51 ++++++++++- .../mgr/dashboard/controllers/orchestrator.py | 17 ---- src/pybind/mgr/dashboard/controllers/osd.py | 54 ++++++++++-- .../osd-creation-preview-modal.component.html | 4 +- ...d-creation-preview-modal.component.spec.ts | 11 ++- .../osd-creation-preview-modal.component.ts | 43 ++++++---- .../osd/osd-form/drive-groups.interface.ts | 3 + .../osd/osd-form/osd-form.component.ts | 17 ++-- .../shared/api/orchestrator.service.spec.ts | 12 --- .../app/shared/api/orchestrator.service.ts | 7 -- .../src/app/shared/api/osd.service.spec.ts | 25 ++++++ .../src/app/shared/api/osd.service.ts | 11 +++ .../shared/services/task-message.service.ts | 6 ++ .../mgr/dashboard/services/orchestrator.py | 4 +- .../mgr/dashboard/tests/test_orchestrator.py | 28 +----- src/pybind/mgr/dashboard/tests/test_osd.py | 85 +++++++++++++++++-- 17 files changed, 268 insertions(+), 163 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/drive-groups.interface.ts diff --git a/qa/tasks/mgr/dashboard/test_orchestrator.py b/qa/tasks/mgr/dashboard/test_orchestrator.py index 0f0a22431a9..9f4204379ac 100644 --- a/qa/tasks/mgr/dashboard/test_orchestrator.py +++ b/qa/tasks/mgr/dashboard/test_orchestrator.py @@ -132,56 +132,3 @@ class OrchestratorControllerTest(DashboardTestCase): self.assertStatus(200) self.assertEqual(len(data), 1) self._validate_inventory(node, data[0]) - - """ - def test_service_list(self): - # get all services - data = self._get(self.URL_SERVICE) - self.assertStatus(200) - - sorting_key = lambda svc: '%(nodename)s.%(service_type)s.%(service_instance)s' % svc - test_services = sorted(self.test_data_services, key=sorting_key) - resp_services = sorted(data, key=sorting_key) - self.assertEqual(len(test_services), len(resp_services)) - for test, resp in zip(test_services, resp_services): - self._validate_service(test, resp) - - # get service by hostname - nodename = self.test_data_services[-1]['nodename'] - test_services = sorted(filter(lambda svc: svc['nodename'] == nodename, test_services), - key=sorting_key) - data = self._get('{}?hostname={}'.format(self.URL_SERVICE, nodename)) - resp_services = sorted(data, key=sorting_key) - for test, resp in zip(test_services, resp_services): - self._validate_service(test, resp) - """ - - def test_create_osds(self): - data = { - 'drive_group': { - 'host_pattern': '*', - 'data_devices': { - 'vendor': 'abc', - 'model': 'cba', - 'rotational': True, - 'size': '4 TB' - }, - 'wal_devices': { - 'vendor': 'def', - 'model': 'fed', - 'rotational': False, - 'size': '1 TB' - }, - 'db_devices': { - 'vendor': 'ghi', - 'model': 'ihg', - 'rotational': False, - 'size': '512 GB' - }, - 'wal_slots': 5, - 'db_slots': 5, - 'encrypted': True - } - } - self._post(self.URL_OSD, data) - self.assertStatus(201) diff --git a/qa/tasks/mgr/dashboard/test_osd.py b/qa/tasks/mgr/dashboard/test_osd.py index 111c440ab45..0c17be4f6a1 100644 --- a/qa/tasks/mgr/dashboard/test_osd.py +++ b/qa/tasks/mgr/dashboard/test_osd.py @@ -11,6 +11,13 @@ class OsdTest(DashboardTestCase): AUTH_ROLES = ['cluster-manager'] + @classmethod + def setUpClass(cls): + super(OsdTest, cls).setUpClass() + cls._load_module('test_orchestrator') + cmd = ['orch', 'set', 'backend', 'test_orchestrator'] + cls.mgr_cluster.mon_manager.raw_cluster_cmd(*cmd) + def tearDown(self): self._post('/api/osd/0/mark_in') @@ -82,9 +89,13 @@ class OsdTest(DashboardTestCase): def test_create_lost_destroy_remove(self): # Create - self._post('/api/osd', { - 'uuid': 'f860ca2e-757d-48ce-b74a-87052cad563f', - 'svc_id': 5 + self._task_post('/api/osd', { + 'method': 'bare', + 'data': { + 'uuid': 'f860ca2e-757d-48ce-b74a-87052cad563f', + 'svc_id': 5 + }, + 'tracking_id': 'bare-5' }) self.assertStatus(201) # Lost @@ -97,6 +108,40 @@ class OsdTest(DashboardTestCase): self._post('/api/osd/5/purge') self.assertStatus(200) + def test_create_with_drive_group(self): + data = { + 'method': 'drive_groups', + 'data': { + 'test': { + 'host_pattern': '*', + 'data_devices': { + 'vendor': 'abc', + 'model': 'cba', + 'rotational': True, + 'size': '4 TB' + }, + 'wal_devices': { + 'vendor': 'def', + 'model': 'fed', + 'rotational': False, + 'size': '1 TB' + }, + 'db_devices': { + 'vendor': 'ghi', + 'model': 'ihg', + 'rotational': False, + 'size': '512 GB' + }, + 'wal_slots': 5, + 'db_slots': 5, + 'encrypted': True + } + }, + 'tracking_id': 'test' + } + self._post('/api/osd', data) + self.assertStatus(201) + def test_safe_to_destroy(self): osd_dump = json.loads(self._ceph_cmd(['osd', 'dump', '-f', 'json'])) max_id = max(map(lambda e: e['osd'], osd_dump['osds'])) diff --git a/src/pybind/mgr/dashboard/controllers/orchestrator.py b/src/pybind/mgr/dashboard/controllers/orchestrator.py index a1f088ba2c2..c9172b83942 100644 --- a/src/pybind/mgr/dashboard/controllers/orchestrator.py +++ b/src/pybind/mgr/dashboard/controllers/orchestrator.py @@ -4,11 +4,6 @@ import os.path import time -try: - from ceph.deployment.drive_group import DriveGroupSpec, DriveGroupValidationError -except ImportError: - pass - from . import ApiController, Endpoint, ReadPermission, UpdatePermission from . import RESTController, Task from .. import mgr @@ -121,15 +116,3 @@ class OrchestratorInventory(RESTController): else: device['osd_ids'] = [] return inventory_hosts - - -@ApiController('/orchestrator/osd', Scope.OSD) -class OrchestratorOsd(RESTController): - - @raise_if_no_orchestrator - def create(self, drive_group): - orch = OrchClient.instance() - try: - orch.osds.create(DriveGroupSpec.from_json(drive_group)) - except (ValueError, TypeError, DriveGroupValidationError) as e: - raise DashboardException(e, component='osd') diff --git a/src/pybind/mgr/dashboard/controllers/osd.py b/src/pybind/mgr/dashboard/controllers/osd.py index 373f66329ad..466130d392a 100644 --- a/src/pybind/mgr/dashboard/controllers/osd.py +++ b/src/pybind/mgr/dashboard/controllers/osd.py @@ -3,13 +3,18 @@ from __future__ import absolute_import import json import logging +from ceph.deployment.drive_group import DriveGroupSpecs, DriveGroupValidationError from mgr_util import get_most_recent_rate -from . import ApiController, RESTController, Endpoint, ReadPermission, UpdatePermission +from . import ApiController, RESTController, Endpoint, Task +from . import CreatePermission, ReadPermission, UpdatePermission +from .orchestrator import raise_if_no_orchestrator from .. import mgr +from ..exceptions import DashboardException from ..security import Scope from ..services.ceph_service import CephService, SendCommandError -from ..services.exception import handle_send_command_error +from ..services.exception import handle_send_command_error, handle_orchestrator_error +from ..services.orchestrator import OrchClient from ..tools import str_to_bool try: from typing import Dict, List, Any, Union # noqa: F401 pylint: disable=unused-import @@ -20,6 +25,10 @@ except ImportError: logger = logging.getLogger('controllers.osd') +def osd_task(name, metadata, wait_for=2.0): + return Task("osd/{}".format(name), metadata, wait_for) + + @ApiController('/osd', Scope.OSD) class Osd(RESTController): def list(self): @@ -170,20 +179,47 @@ class Osd(RESTController): id=int(svc_id), yes_i_really_mean_it=True) - def create(self, uuid=None, svc_id=None): - """ - :param uuid: Will be set automatically if the OSD starts up. - :param id: The ID is only used if a valid uuid is given. - :return: + def _create_bare(self, data): + """Create a OSD container that has no associated device. + + :param data: contain attributes to create a bare OSD. + : `uuid`: will be set automatically if the OSD starts up + : `svc_id`: the ID is only used if a valid uuid is given. """ + try: + uuid = data['uuid'] + svc_id = int(data['svc_id']) + except (KeyError, ValueError) as e: + raise DashboardException(e, component='osd', http_status_code=400) + result = CephService.send_command( - 'mon', 'osd create', id=int(svc_id), uuid=uuid) + 'mon', 'osd create', id=svc_id, uuid=uuid) return { 'result': result, - 'svc_id': int(svc_id), + 'svc_id': svc_id, 'uuid': uuid, } + @raise_if_no_orchestrator + @handle_orchestrator_error('osd') + def _create_with_drive_groups(self, drive_groups): + """Create OSDs with DriveGroups.""" + orch = OrchClient.instance() + try: + orch.osds.create(DriveGroupSpecs(drive_groups).drive_groups) + except (ValueError, TypeError, DriveGroupValidationError) as e: + raise DashboardException(e, component='osd') + + @CreatePermission + @osd_task('create', {'tracking_id': '{tracking_id}'}) + def create(self, method, data, tracking_id): # pylint: disable=W0622 + if method == 'bare': + return self._create_bare(data) + if method == 'drive_groups': + return self._create_with_drive_groups(data) + raise DashboardException( + component='osd', http_status_code=400, msg='Unknown method: {}'.format(method)) + @RESTController.Resource('POST') def purge(self, svc_id): """ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-creation-preview-modal/osd-creation-preview-modal.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-creation-preview-modal/osd-creation-preview-modal.component.html index fd0acad491a..1e4b6d989ea 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-creation-preview-modal/osd-creation-preview-modal.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-creation-preview-modal/osd-creation-preview-modal.component.html @@ -7,8 +7,8 @@ [formGroup]="formGroup" novalidate>