mirror of
https://github.com/ceph/ceph
synced 2025-02-24 11:37:37 +00:00
Merge pull request #52222 from rhcs-dashboard/cluster-upgrade-apis
mgr/dashboard: expose cluster upgrade API endpoints Reviewed-by: Adam King <adking@redhat.com> Reviewed-by: Nizamudeen A <nia@redhat.com>
This commit is contained in:
commit
21214ecc0a
@ -1,9 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from ..security import Scope
|
||||
from ..services.cluster import ClusterModel
|
||||
from . import APIDoc, APIRouter, EndpointDoc, RESTController
|
||||
from ..services.exception import handle_orchestrator_error
|
||||
from ..services.orchestrator import OrchClient, OrchFeature
|
||||
from ..tools import str_to_bool
|
||||
from . import APIDoc, APIRouter, CreatePermission, Endpoint, EndpointDoc, \
|
||||
ReadPermission, RESTController, UpdatePermission, allow_empty_body
|
||||
from ._version import APIVersion
|
||||
from .orchestrator import raise_if_no_orchestrator
|
||||
|
||||
|
||||
@APIRouter('/cluster', Scope.CONFIG_OPT)
|
||||
@ -18,4 +25,77 @@ class Cluster(RESTController):
|
||||
@EndpointDoc("Update the cluster status",
|
||||
parameters={'status': (str, 'Cluster Status')})
|
||||
def singleton_set(self, status: str):
|
||||
ClusterModel(status).to_db()
|
||||
ClusterModel(status).to_db() # -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
@APIRouter('/cluster/upgrade', Scope.CONFIG_OPT)
|
||||
@APIDoc("Upgrade Management API", "Upgrade")
|
||||
class ClusterUpgrade(RESTController):
|
||||
@RESTController.MethodMap()
|
||||
@raise_if_no_orchestrator([OrchFeature.UPGRADE_LIST])
|
||||
@handle_orchestrator_error('upgrade')
|
||||
@EndpointDoc("Get the available versions to upgrade",
|
||||
parameters={
|
||||
'image': (str, 'Ceph Image'),
|
||||
'tags': (bool, 'Show all image tags'),
|
||||
'show_all_versions': (bool, 'Show all available versions')
|
||||
})
|
||||
@ReadPermission
|
||||
def list(self, tags: bool = False, image: Optional[str] = None,
|
||||
show_all_versions: Optional[bool] = False) -> Dict:
|
||||
orch = OrchClient.instance()
|
||||
available_upgrades = orch.upgrades.list(image, str_to_bool(tags),
|
||||
str_to_bool(show_all_versions))
|
||||
return available_upgrades
|
||||
|
||||
@Endpoint()
|
||||
@raise_if_no_orchestrator([OrchFeature.UPGRADE_STATUS])
|
||||
@handle_orchestrator_error('upgrade')
|
||||
@EndpointDoc("Get the cluster upgrade status")
|
||||
@ReadPermission
|
||||
def status(self) -> Dict:
|
||||
orch = OrchClient.instance()
|
||||
status = orch.upgrades.status().to_json()
|
||||
return status
|
||||
|
||||
@Endpoint('POST')
|
||||
@raise_if_no_orchestrator([OrchFeature.UPGRADE_START])
|
||||
@handle_orchestrator_error('upgrade')
|
||||
@EndpointDoc("Start the cluster upgrade")
|
||||
@CreatePermission
|
||||
def start(self, image: Optional[str] = None, version: Optional[str] = None,
|
||||
daemon_types: Optional[List[str]] = None, host_placement: Optional[str] = None,
|
||||
services: Optional[List[str]] = None, limit: Optional[int] = None) -> str:
|
||||
orch = OrchClient.instance()
|
||||
start = orch.upgrades.start(image, version, daemon_types, host_placement, services, limit)
|
||||
return start
|
||||
|
||||
@Endpoint('PUT')
|
||||
@raise_if_no_orchestrator([OrchFeature.UPGRADE_PAUSE])
|
||||
@handle_orchestrator_error('upgrade')
|
||||
@EndpointDoc("Pause the cluster upgrade")
|
||||
@UpdatePermission
|
||||
@allow_empty_body
|
||||
def pause(self) -> str:
|
||||
orch = OrchClient.instance()
|
||||
return orch.upgrades.pause()
|
||||
|
||||
@Endpoint('PUT')
|
||||
@raise_if_no_orchestrator([OrchFeature.UPGRADE_RESUME])
|
||||
@handle_orchestrator_error('upgrade')
|
||||
@EndpointDoc("Resume the cluster upgrade")
|
||||
@UpdatePermission
|
||||
@allow_empty_body
|
||||
def resume(self) -> str:
|
||||
orch = OrchClient.instance()
|
||||
return orch.upgrades.resume()
|
||||
|
||||
@Endpoint('PUT')
|
||||
@raise_if_no_orchestrator([OrchFeature.UPGRADE_STOP])
|
||||
@handle_orchestrator_error('upgrade')
|
||||
@EndpointDoc("Stop the cluster upgrade")
|
||||
@UpdatePermission
|
||||
@allow_empty_body
|
||||
def stop(self) -> str:
|
||||
orch = OrchClient.instance()
|
||||
return orch.upgrades.stop()
|
||||
|
@ -2174,6 +2174,200 @@ paths:
|
||||
summary: Update the cluster status
|
||||
tags:
|
||||
- Cluster
|
||||
/api/cluster/upgrade:
|
||||
get:
|
||||
parameters:
|
||||
- default: false
|
||||
description: Show all image tags
|
||||
in: query
|
||||
name: tags
|
||||
schema:
|
||||
type: boolean
|
||||
- allowEmptyValue: true
|
||||
description: Ceph Image
|
||||
in: query
|
||||
name: image
|
||||
schema:
|
||||
type: string
|
||||
- default: false
|
||||
description: Show all available versions
|
||||
in: query
|
||||
name: show_all_versions
|
||||
schema:
|
||||
type: boolean
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/vnd.ceph.api.v1.0+json:
|
||||
type: object
|
||||
description: OK
|
||||
'400':
|
||||
description: Operation exception. Please check the response body for details.
|
||||
'401':
|
||||
description: Unauthenticated access. Please login first.
|
||||
'403':
|
||||
description: Unauthorized access. Please check your permissions.
|
||||
'500':
|
||||
description: Unexpected error. Please check the response body for the stack
|
||||
trace.
|
||||
security:
|
||||
- jwt: []
|
||||
summary: Get the available versions to upgrade
|
||||
tags:
|
||||
- Upgrade
|
||||
/api/cluster/upgrade/pause:
|
||||
put:
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/vnd.ceph.api.v1.0+json:
|
||||
type: object
|
||||
description: Resource updated.
|
||||
'202':
|
||||
content:
|
||||
application/vnd.ceph.api.v1.0+json:
|
||||
type: object
|
||||
description: Operation is still executing. Please check the task queue.
|
||||
'400':
|
||||
description: Operation exception. Please check the response body for details.
|
||||
'401':
|
||||
description: Unauthenticated access. Please login first.
|
||||
'403':
|
||||
description: Unauthorized access. Please check your permissions.
|
||||
'500':
|
||||
description: Unexpected error. Please check the response body for the stack
|
||||
trace.
|
||||
security:
|
||||
- jwt: []
|
||||
summary: Pause the cluster upgrade
|
||||
tags:
|
||||
- Upgrade
|
||||
/api/cluster/upgrade/resume:
|
||||
put:
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/vnd.ceph.api.v1.0+json:
|
||||
type: object
|
||||
description: Resource updated.
|
||||
'202':
|
||||
content:
|
||||
application/vnd.ceph.api.v1.0+json:
|
||||
type: object
|
||||
description: Operation is still executing. Please check the task queue.
|
||||
'400':
|
||||
description: Operation exception. Please check the response body for details.
|
||||
'401':
|
||||
description: Unauthenticated access. Please login first.
|
||||
'403':
|
||||
description: Unauthorized access. Please check your permissions.
|
||||
'500':
|
||||
description: Unexpected error. Please check the response body for the stack
|
||||
trace.
|
||||
security:
|
||||
- jwt: []
|
||||
summary: Resume the cluster upgrade
|
||||
tags:
|
||||
- Upgrade
|
||||
/api/cluster/upgrade/start:
|
||||
post:
|
||||
parameters: []
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
daemon_types:
|
||||
type: string
|
||||
host_placement:
|
||||
type: string
|
||||
image:
|
||||
type: string
|
||||
limit:
|
||||
type: string
|
||||
services:
|
||||
type: string
|
||||
version:
|
||||
type: string
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
content:
|
||||
application/vnd.ceph.api.v1.0+json:
|
||||
type: object
|
||||
description: Resource created.
|
||||
'202':
|
||||
content:
|
||||
application/vnd.ceph.api.v1.0+json:
|
||||
type: object
|
||||
description: Operation is still executing. Please check the task queue.
|
||||
'400':
|
||||
description: Operation exception. Please check the response body for details.
|
||||
'401':
|
||||
description: Unauthenticated access. Please login first.
|
||||
'403':
|
||||
description: Unauthorized access. Please check your permissions.
|
||||
'500':
|
||||
description: Unexpected error. Please check the response body for the stack
|
||||
trace.
|
||||
security:
|
||||
- jwt: []
|
||||
summary: Start the cluster upgrade
|
||||
tags:
|
||||
- Upgrade
|
||||
/api/cluster/upgrade/status:
|
||||
get:
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/vnd.ceph.api.v1.0+json:
|
||||
type: object
|
||||
description: OK
|
||||
'400':
|
||||
description: Operation exception. Please check the response body for details.
|
||||
'401':
|
||||
description: Unauthenticated access. Please login first.
|
||||
'403':
|
||||
description: Unauthorized access. Please check your permissions.
|
||||
'500':
|
||||
description: Unexpected error. Please check the response body for the stack
|
||||
trace.
|
||||
security:
|
||||
- jwt: []
|
||||
summary: Get the cluster upgrade status
|
||||
tags:
|
||||
- Upgrade
|
||||
/api/cluster/upgrade/stop:
|
||||
put:
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/vnd.ceph.api.v1.0+json:
|
||||
type: object
|
||||
description: Resource updated.
|
||||
'202':
|
||||
content:
|
||||
application/vnd.ceph.api.v1.0+json:
|
||||
type: object
|
||||
description: Operation is still executing. Please check the task queue.
|
||||
'400':
|
||||
description: Operation exception. Please check the response body for details.
|
||||
'401':
|
||||
description: Unauthenticated access. Please login first.
|
||||
'403':
|
||||
description: Unauthorized access. Please check your permissions.
|
||||
'500':
|
||||
description: Unexpected error. Please check the response body for the stack
|
||||
trace.
|
||||
security:
|
||||
- jwt: []
|
||||
summary: Stop the cluster upgrade
|
||||
tags:
|
||||
- Upgrade
|
||||
/api/cluster/user:
|
||||
get:
|
||||
description: "\n Get list of ceph users and its respective data\n \
|
||||
@ -11947,6 +12141,8 @@ tags:
|
||||
name: TcmuRunnerPerfCounter
|
||||
- description: Display Telemetry Report
|
||||
name: Telemetry
|
||||
- description: Upgrade Management API
|
||||
name: Upgrade
|
||||
- description: Display User Details
|
||||
name: User
|
||||
- description: Change User Password
|
||||
|
@ -170,6 +170,36 @@ class DaemonManager(ResourceManager):
|
||||
return self.api.daemon_action(daemon_name=daemon_name, action=action, image=image)
|
||||
|
||||
|
||||
class UpgradeManager(ResourceManager):
|
||||
@wait_api_result
|
||||
def list(self, image: Optional[str], tags: bool,
|
||||
show_all_versions: Optional[bool]) -> Dict[Any, Any]:
|
||||
return self.api.upgrade_ls(image, tags, show_all_versions)
|
||||
|
||||
@wait_api_result
|
||||
def status(self):
|
||||
return self.api.upgrade_status()
|
||||
|
||||
@wait_api_result
|
||||
def start(self, image: str, version: str, daemon_types: Optional[List[str]] = None,
|
||||
host_placement: Optional[str] = None, services: Optional[List[str]] = None,
|
||||
limit: Optional[int] = None) -> str:
|
||||
return self.api.upgrade_start(image, version, daemon_types, host_placement, services,
|
||||
limit)
|
||||
|
||||
@wait_api_result
|
||||
def pause(self) -> str:
|
||||
return self.api.upgrade_pause()
|
||||
|
||||
@wait_api_result
|
||||
def resume(self) -> str:
|
||||
return self.api.upgrade_resume()
|
||||
|
||||
@wait_api_result
|
||||
def stop(self) -> str:
|
||||
return self.api.upgrade_stop()
|
||||
|
||||
|
||||
class OrchClient(object):
|
||||
|
||||
_instance = None
|
||||
@ -189,6 +219,7 @@ class OrchClient(object):
|
||||
self.services = ServiceManager(self.api)
|
||||
self.osds = OsdManager(self.api)
|
||||
self.daemons = DaemonManager(self.api)
|
||||
self.upgrades = UpgradeManager(self.api)
|
||||
|
||||
def available(self, features: Optional[List[str]] = None) -> bool:
|
||||
available = self.status()['available']
|
||||
@ -240,3 +271,10 @@ class OrchFeature(object):
|
||||
DEVICE_BLINK_LIGHT = 'blink_device_light'
|
||||
|
||||
DAEMON_ACTION = 'daemon_action'
|
||||
|
||||
UPGRADE_LIST = 'upgrade_ls'
|
||||
UPGRADE_STATUS = 'upgrade_status'
|
||||
UPGRADE_START = 'upgrade_start'
|
||||
UPGRADE_PAUSE = 'upgrade_pause'
|
||||
UPGRADE_RESUME = 'upgrade_resume'
|
||||
UPGRADE_STOP = 'upgrade_stop'
|
||||
|
61
src/pybind/mgr/dashboard/tests/test_cluster_upgrade.py
Normal file
61
src/pybind/mgr/dashboard/tests/test_cluster_upgrade.py
Normal file
@ -0,0 +1,61 @@
|
||||
from ..controllers.cluster import ClusterUpgrade
|
||||
from ..tests import ControllerTestCase, patch_orch
|
||||
from ..tools import NotificationQueue, TaskManager
|
||||
|
||||
|
||||
class ClusterUpgradeControllerTest(ControllerTestCase):
|
||||
URL_CLUSTER_UPGRADE = '/api/cluster/upgrade'
|
||||
|
||||
@classmethod
|
||||
def setup_server(cls):
|
||||
NotificationQueue.start_queue()
|
||||
TaskManager.init()
|
||||
cls.setup_controllers([ClusterUpgrade])
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
NotificationQueue.stop()
|
||||
|
||||
def test_upgrade_list(self):
|
||||
result = ['17.1.0', '16.2.7', '16.2.6', '16.2.5', '16.1.4', '16.1.3']
|
||||
with patch_orch(True) as fake_client:
|
||||
fake_client.upgrades.list.return_value = result
|
||||
self._get('{}?image=quay.io/ceph/ceph:v16.1.0&tags=False&show_all_versions=False'
|
||||
.format(self.URL_CLUSTER_UPGRADE))
|
||||
self.assertStatus(200)
|
||||
self.assertJsonBody(result)
|
||||
|
||||
def test_start_upgrade(self):
|
||||
msg = "Initiating upgrade to 17.2.6"
|
||||
with patch_orch(True) as fake_client:
|
||||
fake_client.upgrades.start.return_value = msg
|
||||
payload = {
|
||||
'version': '17.2.6'
|
||||
}
|
||||
self._post('{}/start'.format(self.URL_CLUSTER_UPGRADE), payload)
|
||||
self.assertStatus(200)
|
||||
self.assertJsonBody(msg)
|
||||
|
||||
def test_pause_upgrade(self):
|
||||
msg = "Paused upgrade to 17.2.6"
|
||||
with patch_orch(True) as fake_client:
|
||||
fake_client.upgrades.pause.return_value = msg
|
||||
self._put('{}/pause'.format(self.URL_CLUSTER_UPGRADE))
|
||||
self.assertStatus(200)
|
||||
self.assertJsonBody(msg)
|
||||
|
||||
def test_resume_upgrade(self):
|
||||
msg = "Resumed upgrade to 17.2.6"
|
||||
with patch_orch(True) as fake_client:
|
||||
fake_client.upgrades.resume.return_value = msg
|
||||
self._put('{}/resume'.format(self.URL_CLUSTER_UPGRADE))
|
||||
self.assertStatus(200)
|
||||
self.assertJsonBody(msg)
|
||||
|
||||
def test_stop_upgrade(self):
|
||||
msg = "Stopped upgrade to 17.2.6"
|
||||
with patch_orch(True) as fake_client:
|
||||
fake_client.upgrades.stop.return_value = msg
|
||||
self._put('{}/stop'.format(self.URL_CLUSTER_UPGRADE))
|
||||
self.assertStatus(200)
|
||||
self.assertJsonBody(msg)
|
@ -869,6 +869,17 @@ class UpgradeStatusSpec(object):
|
||||
self.message = "" # Freeform description
|
||||
self.is_paused: bool = False # Is the upgrade paused?
|
||||
|
||||
def to_json(self) -> dict:
|
||||
return {
|
||||
'in_progress': self.in_progress,
|
||||
'target_image': self.target_image,
|
||||
'which': self.which,
|
||||
'services_complete': self.services_complete,
|
||||
'progress': self.progress,
|
||||
'message': self.message,
|
||||
'is_paused': self.is_paused,
|
||||
}
|
||||
|
||||
|
||||
def handle_type_error(method: FuncT) -> FuncT:
|
||||
@wraps(method)
|
||||
|
Loading…
Reference in New Issue
Block a user