--- /dev/null
+from typing import Optional
+
+from ..security import Scope
+from ..services.rgw_iam import RgwAccounts
+from ..tools import str_to_bool
+from . import APIDoc, APIRouter, EndpointDoc, RESTController, allow_empty_body
+
+
+@APIRouter('rgw/accounts', Scope.RGW)
+@APIDoc("RGW User Accounts API", "RgwUserAccounts")
+class RgwUserAccountsController(RESTController):
+
+ @allow_empty_body
+ def create(self, account_name: Optional[str] = None,
+ account_id: Optional[str] = None, email: Optional[str] = None):
+ return RgwAccounts.create_account(account_name, account_id, email)
+
+ def list(self, detailed: bool = False):
+ detailed = str_to_bool(detailed)
+ return RgwAccounts.get_accounts(detailed)
+
+ @EndpointDoc("Get RGW Account by id",
+ parameters={'account_id': (str, 'Account id')})
+ def get(self, account_id: str):
+ return RgwAccounts.get_account(account_id)
+
+ @EndpointDoc("Delete RGW Account",
+ parameters={'account_id': (str, 'Account id')})
+ def delete(self, account_id):
+ return RgwAccounts.delete_account(account_id)
+
+ @EndpointDoc("Update RGW account info",
+ parameters={'account_id': (str, 'Account id')})
+ @allow_empty_body
+ def set(self, account_id: str, account_name: Optional[str] = None,
+ email: Optional[str] = None):
+ return RgwAccounts.modify_account(account_id, account_name, email)
+
+ @EndpointDoc("Set RGW Account/Bucket quota",
+ parameters={'account_id': (str, 'Account id'),
+ 'max_size': (str, 'Max size')})
+ @RESTController.Resource(method='PUT', path='/quota')
+ @allow_empty_body
+ def set_quota(self, quota_type: str, account_id: str, max_size: str, max_objects: str):
+ return RgwAccounts.set_quota(quota_type, account_id, max_size, max_objects)
+
+ @EndpointDoc("Enable/Disable RGW Account/Bucket quota",
+ parameters={'account_id': (str, 'Account id')})
+ @RESTController.Resource(method='PUT', path='/quota/status')
+ @allow_empty_body
+ def set_quota_status(self, quota_type: str, account_id: str, quota_status: str):
+ return RgwAccounts.set_quota_status(quota_type, account_id, quota_status)
- jwt: []
tags:
- Prometheus
+ /api/rgw/accounts:
+ get:
+ parameters:
+ - default: false
+ in: query
+ name: detailed
+ 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: []
+ tags:
+ - RgwUserAccounts
+ post:
+ parameters: []
+ requestBody:
+ content:
+ application/json:
+ schema:
+ properties:
+ account_id:
+ type: integer
+ account_name:
+ type: integer
+ email:
+ 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: []
+ tags:
+ - RgwUserAccounts
+ /api/rgw/accounts/{account_id}:
+ delete:
+ parameters:
+ - description: Account id
+ in: path
+ name: account_id
+ required: true
+ schema:
+ type: string
+ responses:
+ '202':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: Operation is still executing. Please check the task queue.
+ '204':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: Resource deleted.
+ '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: Delete RGW Account
+ tags:
+ - RgwUserAccounts
+ get:
+ parameters:
+ - description: Account id
+ in: path
+ name: account_id
+ required: true
+ schema:
+ type: string
+ 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 RGW Account by id
+ tags:
+ - RgwUserAccounts
+ put:
+ parameters:
+ - description: Account id
+ in: path
+ name: account_id
+ required: true
+ schema:
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ properties:
+ account_name:
+ type: integer
+ email:
+ type: string
+ type: object
+ 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: Update RGW account info
+ tags:
+ - RgwUserAccounts
+ /api/rgw/accounts/{account_id}/quota:
+ put:
+ parameters:
+ - description: Account id
+ in: path
+ name: account_id
+ required: true
+ schema:
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ properties:
+ max_objects:
+ type: string
+ max_size:
+ description: Max size
+ type: string
+ quota_type:
+ type: string
+ required:
+ - quota_type
+ - max_size
+ - max_objects
+ type: object
+ 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: Set RGW Account/Bucket quota
+ tags:
+ - RgwUserAccounts
+ /api/rgw/accounts/{account_id}/quota/status:
+ put:
+ parameters:
+ - description: Account id
+ in: path
+ name: account_id
+ required: true
+ schema:
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ properties:
+ quota_status:
+ type: string
+ quota_type:
+ type: string
+ required:
+ - quota_type
+ - quota_status
+ type: object
+ 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: Enable/Disable RGW Account/Bucket quota
+ tags:
+ - RgwUserAccounts
/api/rgw/bucket:
get:
parameters:
name: RgwSite
- description: RGW User Management API
name: RgwUser
+- description: RGW User Accounts API
+ name: RgwUserAccounts
- description: '*No description available*'
name: RgwZone
- description: '*No description available*'
from subprocess import SubprocessError
-from typing import List
+from typing import List, Optional
from .. import mgr
from ..exceptions import DashboardException
class RgwAccounts:
- def send_rgw_cmd(self, command: List[str]):
+ @classmethod
+ def send_rgw_cmd(cls, command: List[str]):
try:
exit_code, out, err = mgr.send_rgwadmin_command(command)
except SubprocessError as e:
raise DashboardException(e, component='rgw')
- def get_accounts(self):
+ @classmethod
+ def get_accounts(cls, detailed: bool = False):
+ """
+ Query account Id's, optionally returning full details.
+
+ :param detailed: Boolean to indicate if full account details are required.
+ """
get_accounts_cmd = ['account', 'list']
- return self.send_rgw_cmd(get_accounts_cmd)
+ account_list = cls.send_rgw_cmd(get_accounts_cmd)
+ detailed_account_list = []
+ if detailed:
+ for account in account_list:
+ detailed_account_list.append(cls.get_account(account))
+ return detailed_account_list
+ return account_list
+
+ @classmethod
+ def get_account(cls, account_id: str):
+ get_account_cmd = ['account', 'get', '--account-id', account_id]
+ return cls.send_rgw_cmd(get_account_cmd)
+
+ @classmethod
+ def create_account(cls, account_name: Optional[str] = None,
+ account_id: Optional[str] = None, email: Optional[str] = None):
+ create_accounts_cmd = ['account', 'create']
+
+ if account_name:
+ create_accounts_cmd += ['--account-name', account_name]
+
+ if account_id:
+ create_accounts_cmd += ['--account_id', account_id]
+
+ if email:
+ create_accounts_cmd += ['--email', email]
+
+ return cls.send_rgw_cmd(create_accounts_cmd)
+
+ @classmethod
+ def modify_account(cls, account_id: str, account_name: Optional[str] = None,
+ email: Optional[str] = None):
+ modify_accounts_cmd = ['account', 'modify', '--account-id', account_id]
+
+ if account_name:
+ modify_accounts_cmd += ['--account-name', account_name]
+
+ if email:
+ modify_accounts_cmd += ['--email', email]
+
+ return cls.send_rgw_cmd(modify_accounts_cmd)
+
+ @classmethod
+ def delete_account(cls, account_id: str):
+ modify_accounts_cmd = ['account', 'rm', '--account-id', account_id]
+
+ return cls.send_rgw_cmd(modify_accounts_cmd)
+
+ @classmethod
+ def get_account_stats(cls, account_id: str):
+ account_stats_cmd = ['account', 'stats', '--account-id', account_id]
+
+ return cls.send_rgw_cmd(account_stats_cmd)
+
+ @classmethod
+ def set_quota(cls, quota_type: str, account_id: str, max_size: str, max_objects: str):
+ set_quota_cmd = ['quota', 'set', '--quota-scope', quota_type, '--account-id', account_id,
+ '--max-size', max_size, '--max-objects', max_objects]
+
+ return cls.send_rgw_cmd(set_quota_cmd)
+
+ @classmethod
+ def set_quota_status(cls, quota_type: str, account_id: str, quota_status: str):
+ set_quota_status_cmd = ['quota', quota_status, '--quota-scope', quota_type,
+ '--account-id', account_id]
+
+ return cls.send_rgw_cmd(set_quota_status_cmd)
--- /dev/null
+from unittest import TestCase
+from unittest.mock import patch
+
+from ..controllers.rgw_iam import RgwUserAccountsController
+from ..services.rgw_iam import RgwAccounts
+
+
+class TestRgwUserAccountsController(TestCase):
+
+ @patch.object(RgwAccounts, 'create_account')
+ def test_create_account(self, mock_create_account):
+ mockReturnVal = {
+ "id": "RGW18661471562806836",
+ "tenant": "",
+ "name": "",
+ "email": "",
+ "quota": {
+ "enabled": False,
+ "check_on_raw": False,
+ "max_size": -1,
+ "max_size_kb": 0,
+ "max_objects": -1
+ },
+ "bucket_quota": {
+ "enabled": False,
+ "check_on_raw": False,
+ "max_size": -1,
+ "max_size_kb": 0,
+ "max_objects": -1
+ },
+ "max_users": 1000,
+ "max_roles": 1000,
+ "max_groups": 1000,
+ "max_buckets": 1000,
+ "max_access_keys": 4
+ }
+
+ # Mock the return value of the create_account method
+ mock_create_account.return_value = mockReturnVal
+
+ controller = RgwUserAccountsController()
+ result = controller.create(account_name='test_account', account_id='RGW18661471562806836',
+ email='test@example.com')
+
+ # Check if the account creation method was called with the correct parameters
+ mock_create_account.assert_called_with('test_account', 'RGW18661471562806836',
+ 'test@example.com')
+ # Check the returned result
+ self.assertEqual(result, mockReturnVal)
+
+ @patch.object(RgwAccounts, 'get_accounts')
+ def test_list_accounts(self, mock_get_accounts):
+ mock_return_value = [
+ "RGW22222222222222222",
+ "RGW59378973811515857",
+ "RGW11111111111111111"
+ ]
+
+ mock_get_accounts.return_value = mock_return_value
+
+ controller = RgwUserAccountsController()
+ result = controller.list(detailed=False)
+
+ mock_get_accounts.assert_called_with(False)
+
+ self.assertEqual(result, mock_return_value)
+
+ @patch.object(RgwAccounts, 'get_accounts')
+ def test_list_accounts_with_details(self, mock_get_accounts):
+ mock_return_value = [
+ {
+ "id": "RGW22222222222222222",
+ "tenant": "",
+ "name": "Account2",
+ "email": "account2@ceph.com",
+ "quota": {
+ "enabled": False,
+ "check_on_raw": False,
+ "max_size": -1,
+ "max_size_kb": 0,
+ "max_objects": -1
+ },
+ "bucket_quota": {
+ "enabled": False,
+ "check_on_raw": False,
+ "max_size": -1,
+ "max_size_kb": 0,
+ "max_objects": -1
+ },
+ "max_users": 1000,
+ "max_roles": 1000,
+ "max_groups": 1000,
+ "max_buckets": 1000,
+ "max_access_keys": 4
+ },
+ {
+ "id": "RGW11111111111111111",
+ "tenant": "",
+ "name": "Account1",
+ "email": "account1@ceph.com",
+ "quota": {
+ "enabled": False,
+ "check_on_raw": False,
+ "max_size": -1,
+ "max_size_kb": 0,
+ "max_objects": -1
+ },
+ "bucket_quota": {
+ "enabled": False,
+ "check_on_raw": False,
+ "max_size": -1,
+ "max_size_kb": 0,
+ "max_objects": -1
+ },
+ "max_users": 1000,
+ "max_roles": 1000,
+ "max_groups": 1000,
+ "max_buckets": 1000,
+ "max_access_keys": 4
+ }
+ ]
+
+ mock_get_accounts.return_value = mock_return_value
+
+ controller = RgwUserAccountsController()
+ result = controller.list(detailed=True)
+
+ mock_get_accounts.assert_called_with(True)
+
+ self.assertEqual(result, mock_return_value)
+
+ @patch.object(RgwAccounts, 'get_account')
+ def test_get_account(self, mock_get_account):
+ mock_return_value = {
+ "id": "RGW22222222222222222",
+ "tenant": "",
+ "name": "Account2",
+ "email": "account2@ceph.com",
+ "quota": {
+ "enabled": False,
+ "check_on_raw": False,
+ "max_size": -1,
+ "max_size_kb": 0,
+ "max_objects": -1
+ },
+ "bucket_quota": {
+ "enabled": False,
+ "check_on_raw": False,
+ "max_size": -1,
+ "max_size_kb": 0,
+ "max_objects": -1
+ },
+ "max_users": 1000,
+ "max_roles": 1000,
+ "max_groups": 1000,
+ "max_buckets": 1000,
+ "max_access_keys": 4
+ }
+ mock_get_account.return_value = mock_return_value
+
+ controller = RgwUserAccountsController()
+ result = controller.get(account_id='RGW22222222222222222')
+
+ mock_get_account.assert_called_with('RGW22222222222222222')
+
+ self.assertEqual(result, mock_return_value)
+
+ @patch.object(RgwAccounts, 'delete_account')
+ def test_delete_account(self, mock_delete_account):
+ mock_delete_account.return_value = None
+
+ controller = RgwUserAccountsController()
+ result = controller.delete(account_id='RGW59378973811515857')
+
+ mock_delete_account.assert_called_with('RGW59378973811515857')
+
+ self.assertEqual(result, None)
+
+ @patch.object(RgwAccounts, 'modify_account')
+ def test_set_account_name(self, mock_modify_account):
+ mock_return_value = mock_return_value = {
+ "id": "RGW59378973811515857",
+ "tenant": "",
+ "name": "new_account_name",
+ "email": "new_email@example.com",
+ "quota": {
+ "enabled": False,
+ "check_on_raw": False,
+ "max_size": -1,
+ "max_size_kb": 0,
+ "max_objects": -1
+ },
+ "bucket_quota": {
+ "enabled": False,
+ "check_on_raw": False,
+ "max_size": -1,
+ "max_size_kb": 0,
+ "max_objects": -1
+ },
+ "max_users": 1000,
+ "max_roles": 1000,
+ "max_groups": 1000,
+ "max_buckets": 1000,
+ "max_access_keys": 4
+ }
+ mock_modify_account.return_value = mock_return_value
+
+ controller = RgwUserAccountsController()
+ result = controller.set(account_id='RGW59378973811515857', account_name='new_account_name',
+ email='new_email@example.com')
+
+ mock_modify_account.assert_called_with('RGW59378973811515857', 'new_account_name',
+ 'new_email@example.com')
+
+ self.assertEqual(result, mock_return_value)
+
+ @patch.object(RgwAccounts, 'set_quota')
+ def test_set_quota(self, mock_set_quota):
+ mock_return_value = {
+ "id": "RGW11111111111111111",
+ "tenant": "",
+ "name": "Account1",
+ "email": "account1@ceph.com",
+ "quota": {
+ "enabled": False,
+ "check_on_raw": False,
+ "max_size": 10737418240,
+ "max_size_kb": 10485760,
+ "max_objects": 1000000
+ },
+ "bucket_quota": {
+ "enabled": False,
+ "check_on_raw": False,
+ "max_size": -1,
+ "max_size_kb": 0,
+ "max_objects": 1000000
+ },
+ "max_users": 1000,
+ "max_roles": 1000,
+ "max_groups": 1000,
+ "max_buckets": 1000,
+ "max_access_keys": 4
+ }
+
+ mock_set_quota.return_value = mock_return_value
+
+ controller = RgwUserAccountsController()
+ result = controller.set_quota(quota_type='account', account_id='RGW11111111111111111',
+ max_size='10GB', max_objects='1000')
+
+ mock_set_quota.assert_called_with('account', 'RGW11111111111111111', '10GB', '1000')
+
+ self.assertEqual(result, mock_return_value)
+
+ @patch.object(RgwAccounts, 'set_quota_status')
+ def test_set_quota_status(self, mock_set_quota_status):
+ mock_return_value = {
+ "id": "RGW11111111111111111",
+ "tenant": "",
+ "name": "Account1",
+ "email": "account1@ceph.com",
+ "quota": {
+ "enabled": True,
+ "check_on_raw": False,
+ "max_size": 10737418240,
+ "max_size_kb": 10485760,
+ "max_objects": 1000000
+ },
+ "bucket_quota": {
+ "enabled": False,
+ "check_on_raw": False,
+ "max_size": -1,
+ "max_size_kb": 0,
+ "max_objects": 1000000
+ },
+ "max_users": 1000,
+ "max_roles": 1000,
+ "max_groups": 1000,
+ "max_buckets": 1000,
+ "max_access_keys": 4
+ }
+
+ mock_set_quota_status.return_value = mock_return_value
+
+ controller = RgwUserAccountsController()
+ result = controller.set_quota_status(quota_type='account',
+ account_id='RGW11111111111111111',
+ quota_status='enabled')
+
+ mock_set_quota_status.assert_called_with('account', 'RGW11111111111111111', 'enabled')
+
+ self.assertEqual(result, mock_return_value)