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:
Nizamudeen A 2023-07-11 15:49:55 +05:30 committed by GitHub
commit 21214ecc0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 388 additions and 2 deletions

View File

@ -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()

View File

@ -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

View File

@ -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'

View 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)

View File

@ -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)