# -*- 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)
@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()
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 \
name: TcmuRunnerPerfCounter
- description: Display Telemetry Report
name: Telemetry
+- description: Upgrade Management API
+ name: Upgrade
- description: Display User Details
name: User
- description: Change User Password
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
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']
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'
--- /dev/null
+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)
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)