Skip to content

Commit 1e333f3

Browse files
authored
Merge pull request ceph#62027 from rhcs-dashboard/admin-ops-api-for-rgw-accounts
mgr/dashboard: Use admin ops API for rgw accounts
2 parents 7846269 + 78d0a26 commit 1e333f3

File tree

6 files changed

+337
-250
lines changed

6 files changed

+337
-250
lines changed

src/pybind/mgr/dashboard/controllers/rgw_iam.py

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from typing import Optional
22

3+
from ..controllers.rgw import RgwRESTController
34
from ..security import Scope
45
from ..services.rgw_iam import RgwAccounts
56
from ..tools import str_to_bool
@@ -8,7 +9,7 @@
89

910
@APIRouter('rgw/accounts', Scope.RGW)
1011
@APIDoc("RGW User Accounts API", "RgwUserAccounts")
11-
class RgwUserAccountsController(RESTController):
12+
class RgwUserAccountsController(RgwRESTController):
1213
@EndpointDoc("Update RGW account info",
1314
parameters={'account_name': (str, 'Account name'),
1415
'email': (str, 'Email'),
@@ -17,12 +18,13 @@ class RgwUserAccountsController(RESTController):
1718
'max_users': (int, 'Max users'),
1819
'max_roles': (int, 'Max roles'),
1920
'max_group': (int, 'Max groups'),
20-
'max_access_keys': (int, 'Max access keys')})
21+
'max_access_keys': (int, 'Max access keys'),
22+
'daemon_name': (str, 'Name of the daemon')})
2123
@allow_empty_body
2224
def create(self, account_name: str, tenant: Optional[str] = None,
2325
email: Optional[str] = None, max_buckets: Optional[int] = None,
2426
max_users: Optional[int] = None, max_roles: Optional[int] = None,
25-
max_group: Optional[int] = None,
27+
max_group: Optional[int] = None, daemon_name=None,
2628
max_access_keys: Optional[int] = None):
2729
"""
2830
Create an account
@@ -31,38 +33,64 @@ def create(self, account_name: str, tenant: Optional[str] = None,
3133
:return: Returns account resource.
3234
:rtype: Dict[str, Any]
3335
"""
34-
return RgwAccounts.create_account(account_name, tenant, email,
35-
max_buckets, max_users, max_roles,
36-
max_group, max_access_keys)
36+
params = {'name': account_name}
37+
if tenant:
38+
params['tenant'] = tenant
39+
if email:
40+
params['email'] = email
41+
if max_buckets:
42+
params['max-buckets'] = str(max_buckets)
43+
if max_users:
44+
params['max-users'] = str(max_users)
45+
if max_roles:
46+
params['max-roles'] = str(max_roles)
47+
if max_group:
48+
params['max-group'] = str(max_group)
49+
if max_access_keys:
50+
params['max-access-keys'] = str(max_access_keys)
3751

38-
def list(self, detailed: bool = False):
52+
result = self.proxy(daemon_name, 'POST', 'account', params)
53+
return result
54+
55+
def list(self, daemon_name=None, detailed: bool = False):
3956
"""
4057
List all account ids or all detailed account info based on the 'detailed' query parameter.
4158
4259
- If detailed=True, returns detailed account info.
4360
- If detailed=False, returns only account ids.
4461
"""
4562
detailed = str_to_bool(detailed)
46-
return RgwAccounts.get_accounts(detailed)
63+
account_list = RgwAccounts.get_accounts()
64+
detailed_account_list = []
65+
if detailed:
66+
for account in account_list:
67+
detailed_account_list.append(self.get_account(account, daemon_name))
68+
return detailed_account_list
69+
return account_list
4770

4871
@EndpointDoc("Get RGW Account by id",
49-
parameters={'account_id': (str, 'Account id')})
50-
def get(self, account_id: str):
72+
parameters={'account_id': (str, 'Account id'),
73+
'daemon_name': (str, 'Name of the daemon')})
74+
def get(self, account_id: str, daemon_name=None):
5175
"""
5276
Get an account by account id
5377
"""
54-
return RgwAccounts.get_account(account_id)
78+
return self.get_account(account_id, daemon_name)
79+
80+
def get_account(self, account_id, daemon_name=None) -> dict:
81+
return self.proxy(daemon_name, 'GET', 'account', {'id': account_id})
5582

5683
@EndpointDoc("Delete RGW Account",
57-
parameters={'account_id': (str, 'Account id')})
58-
def delete(self, account_id):
84+
parameters={'account_id': (str, 'Account id'),
85+
'daemon_name': (str, 'Name of the daemon')})
86+
def delete(self, account_id, daemon_name=None):
5987
"""
6088
Removes an account
6189
6290
:param account_id: account identifier
6391
:return: None.
6492
"""
65-
return RgwAccounts.delete_account(account_id)
93+
return self.proxy(daemon_name, 'DELETE', 'account', {'id': account_id}, json_response=False)
6694

6795
@EndpointDoc("Update RGW account info",
6896
parameters={'account_id': (str, 'Account id'),
@@ -73,23 +101,41 @@ def delete(self, account_id):
73101
'max_users': (int, 'Max users'),
74102
'max_roles': (int, 'Max roles'),
75103
'max_group': (int, 'Max groups'),
76-
'max_access_keys': (int, 'Max access keys')})
104+
'max_access_keys': (int, 'Max access keys'),
105+
'daemon_name': (str, 'Name of the daemon')})
77106
@allow_empty_body
78107
def set(self, account_id: str, account_name: str,
79108
email: Optional[str] = None, tenant: Optional[str] = None,
80109
max_buckets: Optional[int] = None, max_users: Optional[int] = None,
81110
max_roles: Optional[int] = None, max_group: Optional[int] = None,
82-
max_access_keys: Optional[int] = None):
111+
max_access_keys: Optional[int] = None, daemon_name=None):
83112
"""
84113
Modifies an account
85114
86115
:param account_id: Account identifier
87116
:return: Returns modified account resource.
88117
:rtype: Dict[str, Any]
89118
"""
90-
return RgwAccounts.modify_account(account_id, account_name, email, tenant,
91-
max_buckets, max_users, max_roles,
92-
max_group, max_access_keys)
119+
120+
params = {'id': account_id}
121+
if account_name:
122+
params['name'] = account_name
123+
if tenant:
124+
params['tenant'] = tenant
125+
if email:
126+
params['email'] = email
127+
if max_buckets:
128+
params['max-buckets'] = str(max_buckets)
129+
if max_users:
130+
params['max-users'] = str(max_users)
131+
if max_roles:
132+
params['max-roles'] = str(max_roles)
133+
if max_group:
134+
params['max-group'] = str(max_group)
135+
if max_access_keys:
136+
params['max-access-keys'] = str(max_access_keys)
137+
138+
return self.proxy(daemon_name, 'PUT', 'account', params)
93139

94140
@EndpointDoc("Set RGW Account/Bucket quota",
95141
parameters={'account_id': (str, 'Account id'),

src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user-accounts.service.spec.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { TestBed } from '@angular/core/testing';
33
import { RgwUserAccountsService } from './rgw-user-accounts.service';
44
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
55
import { Account } from '~/app/ceph/rgw/models/rgw-user-accounts';
6+
import { RgwHelper } from '~/testing/unit-test-helper';
67

78
const mockAccountData: Account[] = [
89
{
@@ -68,6 +69,7 @@ describe('RgwUserAccountsService', () => {
6869
});
6970
service = TestBed.inject(RgwUserAccountsService);
7071
httpTesting = TestBed.inject(HttpTestingController);
72+
RgwHelper.selectDaemon();
7173
});
7274

7375
it('should be created', () => {
@@ -76,8 +78,56 @@ describe('RgwUserAccountsService', () => {
7678

7779
it('should fetch detailed list of accounts', () => {
7880
service.list(true).subscribe();
79-
const req = httpTesting.expectOne('api/rgw/accounts?detailed=true');
81+
const req = httpTesting.expectOne(
82+
`api/rgw/accounts?${RgwHelper.DAEMON_QUERY_PARAM}&detailed=true`
83+
);
8084
expect(req.request.method).toBe('GET');
8185
req.flush(mockAccountData);
8286
});
87+
88+
it('should fetch single account detail', () => {
89+
service.get('RGW80617806988089685').subscribe();
90+
const req = httpTesting.expectOne(
91+
`api/rgw/accounts/get?${RgwHelper.DAEMON_QUERY_PARAM}&account_id=RGW80617806988089685`
92+
);
93+
expect(req.request.method).toBe('GET');
94+
req.flush(mockAccountData[0]);
95+
});
96+
97+
it('should call create method', () => {
98+
service.create(mockAccountData[0]).subscribe();
99+
const req = httpTesting.expectOne(`api/rgw/accounts?${RgwHelper.DAEMON_QUERY_PARAM}`);
100+
expect(req.request.method).toBe('POST');
101+
req.flush(mockAccountData[0]);
102+
});
103+
104+
it('should call modify method', () => {
105+
service.modify(mockAccountData[0]).subscribe();
106+
const req = httpTesting.expectOne(`api/rgw/accounts/set?${RgwHelper.DAEMON_QUERY_PARAM}`);
107+
expect(req.request.method).toBe('PUT');
108+
req.flush(mockAccountData[0]);
109+
});
110+
111+
it('should call remove method', () => {
112+
service.remove('RGW12444466134482748').subscribe();
113+
const req = httpTesting.expectOne(
114+
`api/rgw/accounts/RGW12444466134482748?${RgwHelper.DAEMON_QUERY_PARAM}`
115+
);
116+
expect(req.request.method).toBe('DELETE');
117+
req.flush(null);
118+
});
119+
120+
it('should call setQuota method', () => {
121+
service
122+
.setQuota('RGW12444466134482748', {
123+
quota_type: 'bucket',
124+
max_size: '10MB',
125+
max_objects: '1000',
126+
enabled: true
127+
})
128+
.subscribe();
129+
const req = httpTesting.expectOne(`api/rgw/accounts/RGW12444466134482748/quota`);
130+
expect(req.request.method).toBe('PUT');
131+
req.flush(null);
132+
});
83133
});

src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user-accounts.service.ts

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,51 @@
11
import { HttpClient, HttpParams } from '@angular/common/http';
22
import { Injectable } from '@angular/core';
33
import { Observable } from 'rxjs';
4+
import { RgwDaemonService } from './rgw-daemon.service';
5+
import { Account } from '~/app/ceph/rgw/models/rgw-user-accounts';
46

57
@Injectable({
68
providedIn: 'root'
79
})
810
export class RgwUserAccountsService {
911
private url = 'api/rgw/accounts';
1012

11-
constructor(private http: HttpClient) {}
13+
constructor(private http: HttpClient, private rgwDaemonService: RgwDaemonService) {}
1214

1315
list(detailed?: boolean): Observable<any> {
14-
let params = new HttpParams();
15-
if (detailed) {
16-
params = params.append('detailed', detailed);
17-
}
18-
return this.http.get(this.url, { params });
16+
return this.rgwDaemonService.request((params: HttpParams) => {
17+
if (detailed) {
18+
params = params.append('detailed', detailed);
19+
}
20+
return this.http.get(this.url, { params });
21+
});
1922
}
2023

2124
get(account_id: string): Observable<any> {
22-
let params = new HttpParams();
23-
if (account_id) {
24-
params = params.append('account_id', account_id);
25-
}
26-
return this.http.get(`${this.url}/get`, { params });
25+
return this.rgwDaemonService.request((params: HttpParams) => {
26+
if (account_id) {
27+
params = params.append('account_id', account_id);
28+
}
29+
return this.http.get(`${this.url}/get`, { params });
30+
});
2731
}
2832

29-
create(payload: any): Observable<any> {
30-
return this.http.post(this.url, payload);
33+
create(payload: Partial<Account>): Observable<any> {
34+
return this.rgwDaemonService.request((params: HttpParams) => {
35+
return this.http.post(this.url, payload, { params: params });
36+
});
3137
}
3238

33-
modify(payload: any): Observable<any> {
34-
return this.http.put(`${this.url}/set`, payload);
39+
modify(payload: Partial<Account>): Observable<any> {
40+
return this.rgwDaemonService.request((params: HttpParams) => {
41+
return this.http.put(`${this.url}/set`, payload, { params: params });
42+
});
3543
}
3644

3745
remove(accountId: string) {
38-
return this.http.delete(`${this.url}/${accountId}`);
46+
return this.rgwDaemonService.request((params: HttpParams) => {
47+
return this.http.delete(`${this.url}/${accountId}`, { params: params });
48+
});
3949
}
4050

4151
setQuota(

src/pybind/mgr/dashboard/openapi.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11040,6 +11040,11 @@ paths:
1104011040
\ detailed account info.\n - If detailed=False, returns only account\
1104111041
\ ids.\n "
1104211042
parameters:
11043+
- allowEmptyValue: true
11044+
in: query
11045+
name: daemon_name
11046+
schema:
11047+
type: string
1104311048
- default: false
1104411049
in: query
1104511050
name: detailed
@@ -11077,6 +11082,9 @@ paths:
1107711082
account_name:
1107811083
description: Account name
1107911084
type: string
11085+
daemon_name:
11086+
description: Name of the daemon
11087+
type: string
1108011088
email:
1108111089
description: Email
1108211090
type: string
@@ -11137,6 +11145,12 @@ paths:
1113711145
required: true
1113811146
schema:
1113911147
type: string
11148+
- allowEmptyValue: true
11149+
description: Name of the daemon
11150+
in: query
11151+
name: daemon_name
11152+
schema:
11153+
type: string
1114011154
responses:
1114111155
'202':
1114211156
content:
@@ -11171,6 +11185,12 @@ paths:
1117111185
required: true
1117211186
schema:
1117311187
type: string
11188+
- allowEmptyValue: true
11189+
description: Name of the daemon
11190+
in: query
11191+
name: daemon_name
11192+
schema:
11193+
type: string
1117411194
responses:
1117511195
'200':
1117611196
content:
@@ -11210,6 +11230,9 @@ paths:
1121011230
account_name:
1121111231
description: Account name
1121211232
type: string
11233+
daemon_name:
11234+
description: Name of the daemon
11235+
type: string
1121311236
email:
1121411237
description: Email
1121511238
type: string

0 commit comments

Comments
 (0)