Merge pull request #61183 from rhcs-dashboard/smb-join_auth-usersgroups-mgmt

mgr/dashboard: smb join-auth and usersgroups resources listing

Reviewed-by: Afreen Misbah <afreen@ibm.com>
This commit is contained in:
afreen23 2025-02-11 12:22:21 +05:30 committed by GitHub
commit d361ca0cd8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 862 additions and 19 deletions

View File

@ -88,6 +88,41 @@ SHARE_SCHEMA = {
}, "Configuration for the CephFS share")
}
JOIN_AUTH_SCHEMA = {
"resource_type": (str, "ceph.smb.join.auth"),
"auth_id": (str, "Unique identifier for the join auth resource"),
"intent": (str, "Desired state of the resource, e.g., 'present' or 'removed'"),
"auth": ({
"username": (str, "Username for authentication"),
"password": (str, "Password for authentication")
}, "Authentication credentials"),
"linked_to_cluster": (str, "Optional string containing a cluster ID. \
If set, the resource is linked to the cluster and will be automatically removed \
when the cluster is removed")
}
LIST_JOIN_AUTH_SCHEMA = [JOIN_AUTH_SCHEMA]
USERSGROUPS_SCHEMA = {
"resource_type": (str, "ceph.smb.usersgroups"),
"users_groups_id": (str, "A short string identifying the usersgroups resource"),
"intent": (str, "Desired state of the resource, e.g., 'present' or 'removed'"),
"values": ({
"users": ([{
"name": (str, "The user name"),
"password": (str, "The password for the user")
}], "List of user objects, each containing a name and password"),
"groups": ([{
"name": (str, "The name of the group")
}], "List of group objects, each containing a name")
}, "Required object containing users and groups information"),
"linked_to_cluster": (str, "Optional string containing a cluster ID. \
If set, the resource is linked to the cluster and will be automatically removed \
when the cluster is removed")
}
LIST_USERSGROUPS_SCHEMA = [USERSGROUPS_SCHEMA]
def raise_on_failure(func):
@wraps(func)
@ -227,6 +262,50 @@ class SMBShare(RESTController):
return mgr.remote('smb', 'apply_resources', json.dumps(resource)).one().to_simplified()
@APIRouter('/smb/joinauth', Scope.SMB)
@APIDoc("SMB Join Auth API", "SMB")
class SMBJoinAuth(RESTController):
_resource: str = 'ceph.smb.join.auth'
@ReadPermission
@EndpointDoc("List smb join authorization resources",
responses={200: LIST_JOIN_AUTH_SCHEMA})
def list(self, join_auth: str = '') -> List[Share]:
"""
List all smb join auth resources
:return: Returns list of join auth.
:rtype: List[Dict]
"""
res = mgr.remote(
'smb',
'show',
[f'{self._resource}.{join_auth}' if join_auth else self._resource])
return res['resources'] if 'resources' in res else [res]
@APIRouter('/smb/usersgroups', Scope.SMB)
@APIDoc("SMB Users Groups API", "SMB")
class SMBUsersgroups(RESTController):
_resource: str = 'ceph.smb.usersgroups'
@ReadPermission
@EndpointDoc("List smb user resources",
responses={200: LIST_USERSGROUPS_SCHEMA})
def list(self, users_groups: str = '') -> List[Share]:
"""
List all smb usersgroups resources
:return: Returns list of usersgroups.
:rtype: List[Dict]
"""
res = mgr.remote(
'smb',
'show',
[f'{self._resource}.{users_groups}' if users_groups else self._resource])
return res['resources'] if 'resources' in res else [res]
@UIRouter('/smb')
class SMBStatus(RESTController):
@EndpointDoc("Get SMB feature status")

View File

@ -51,8 +51,8 @@ import { UpgradeProgressComponent } from './ceph/cluster/upgrade/upgrade-progres
import { MultiClusterComponent } from './ceph/cluster/multi-cluster/multi-cluster.component';
import { MultiClusterListComponent } from './ceph/cluster/multi-cluster/multi-cluster-list/multi-cluster-list.component';
import { MultiClusterDetailsComponent } from './ceph/cluster/multi-cluster/multi-cluster-details/multi-cluster-details.component';
import { SmbClusterListComponent } from './ceph/smb/smb-cluster-list/smb-cluster-list.component';
import { SmbClusterFormComponent } from './ceph/smb/smb-cluster-form/smb-cluster-form.component';
import { SmbTabsComponent } from './ceph/smb/smb-tabs/smb-tabs.component';
@Injectable()
export class PerformanceCounterBreadcrumbsResolver extends BreadcrumbsResolver {
@ -446,7 +446,7 @@ const routes: Routes = [
breadcrumbs: 'File/SMB'
},
children: [
{ path: '', component: SmbClusterListComponent },
{ path: '', component: SmbTabsComponent },
{
path: `${URLVerbs.CREATE}`,
component: SmbClusterFormComponent,

View File

@ -9,7 +9,7 @@ import {
CLUSTERING,
PLACEMENT,
RequestModel,
RESOURCE_TYPE,
CLUSTER_RESOURCE,
RESOURCE,
DomainSettings,
JoinSource
@ -214,7 +214,7 @@ export class SmbClusterFormComponent extends CdForm implements OnInit {
const requestModel: RequestModel = {
cluster_resource: {
resource_type: RESOURCE_TYPE,
resource_type: CLUSTER_RESOURCE,
cluster_id: rawFormValue.cluster_id,
auth_mode: rawFormValue.auth_mode
}

View File

@ -1,7 +1,7 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SmbClusterTabsComponent } from './smb-cluster-tabs.component';
import { RESOURCE_TYPE, SMBCluster } from '../smb.model';
import { CLUSTER_RESOURCE, SMBCluster } from '../smb.model';
import { By } from '@angular/platform-browser';
describe('SmbClusterTabsComponent', () => {
@ -32,7 +32,7 @@ describe('SmbClusterTabsComponent', () => {
const selectedSmbCluster = (clusterId: string) => {
const smbCluster: SMBCluster = {
resource_type: RESOURCE_TYPE,
resource_type: CLUSTER_RESOURCE,
cluster_id: clusterId,
auth_mode: 'user'
};

View File

@ -0,0 +1,9 @@
<cd-table
[data]="joinAuth$ | async"
columnMode="flex"
[columns]="columns"
selectionType="single"
[hasDetails]="false"
(fetchData)="loadJoinAuth()"
>
</cd-table>

View File

@ -0,0 +1,27 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SmbJoinAuthListComponent } from './smb-join-auth-list.component';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastrModule } from 'ngx-toastr';
import { SharedModule } from '~/app/shared/shared.module';
describe('SmbJoinAuthListComponent', () => {
let component: SmbJoinAuthListComponent;
let fixture: ComponentFixture<SmbJoinAuthListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [SmbJoinAuthListComponent],
imports: [SharedModule, HttpClientTestingModule, ToastrModule.forRoot(), RouterTestingModule]
}).compileComponents();
fixture = TestBed.createComponent(SmbJoinAuthListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,69 @@
import { Component, OnInit } from '@angular/core';
import { Observable, BehaviorSubject, of } from 'rxjs';
import { switchMap, catchError } from 'rxjs/operators';
import { SmbService } from '~/app/shared/api/smb.service';
import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
import { CdTableAction } from '~/app/shared/models/cd-table-action';
import { CdTableColumn } from '~/app/shared/models/cd-table-column';
import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
import { Permission } from '~/app/shared/models/permissions';
import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
import { SMBJoinAuth } from '../smb.model';
@Component({
selector: 'cd-smb-join-auth-list',
templateUrl: './smb-join-auth-list.component.html',
styleUrls: ['./smb-join-auth-list.component.scss']
})
export class SmbJoinAuthListComponent implements OnInit {
columns: CdTableColumn[];
permission: Permission;
tableActions: CdTableAction[];
context: CdTableFetchDataContext;
joinAuth$: Observable<SMBJoinAuth[]>;
subject$ = new BehaviorSubject<SMBJoinAuth[]>([]);
constructor(
private authStorageService: AuthStorageService,
public actionLabels: ActionLabelsI18n,
private smbService: SmbService
) {
this.permission = this.authStorageService.getPermissions().smb;
}
ngOnInit() {
this.columns = [
{
name: $localize`ID`,
prop: 'auth_id',
flexGrow: 2
},
{
name: $localize`Username`,
prop: 'auth.username',
flexGrow: 2
},
{
name: $localize`Linked to Cluster`,
prop: 'linked_to_cluster',
flexGrow: 2
}
];
this.joinAuth$ = this.subject$.pipe(
switchMap(() =>
this.smbService.listJoinAuths().pipe(
catchError(() => {
this.context.error();
return of(null);
})
)
)
);
}
loadJoinAuth() {
this.subject$.next([]);
}
}

View File

@ -0,0 +1,66 @@
<legend
*ngIf="selectedTab == Tabs.clusters"
i18n
>
Clusters
<cd-help-text>
Logical management units that may map to one or more managed Samba service
</cd-help-text>
</legend>
<legend
*ngIf="selectedTab == Tabs.joinAuths"
i18n
>
Active directory access resources
<cd-help-text>
Logical management units for authorization on Active Directory servers
</cd-help-text>
</legend>
<legend
*ngIf="selectedTab == Tabs.usersgroups"
i18n
>
Standalone access resources
<cd-help-text>
Logical management units for authorization on Standalone servers
</cd-help-text>
</legend>
<cds-tabs
type="contained"
followFocus="true"
isNavigation="true"
[cacheActive]="false">
<cds-tab
heading="Clusters"
[tabContent]="clusters"
i18n-heading
(selected)="onSelected(Tabs.clusters)">
</cds-tab>
<cds-tab
heading="Active Directory"
[tabContent]="joinAuth"
i18n-heading
(selected)="onSelected(Tabs.joinAuths)">
</cds-tab>
<cds-tab
heading="Standalone"
[tabContent]="usersgroups"
i18n-heading
(selected)="onSelected(Tabs.usersgroups)">
</cds-tab>
</cds-tabs>
<ng-template #clusters>
<cd-smb-cluster-list></cd-smb-cluster-list>
</ng-template>
<ng-template #joinAuth>
<cd-smb-join-auth-list></cd-smb-join-auth-list>
</ng-template>
<ng-template #usersgroups>
<cd-smb-users-list></cd-smb-users-list>
</ng-template>

View File

@ -0,0 +1,38 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SmbTabsComponent } from './smb-tabs.component';
import { By } from '@angular/platform-browser';
describe('SmbTabsComponent', () => {
let component: SmbTabsComponent;
let fixture: ComponentFixture<SmbTabsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [SmbTabsComponent]
}).compileComponents();
fixture = TestBed.createComponent(SmbTabsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should display the heading text in the tab', () => {
const tabs = fixture.debugElement.queryAll(By.css('cds-tab'));
expect(tabs.length).toBe(3);
expect(tabs[0].attributes['heading']).toBe('Clusters');
expect(tabs[1].attributes['heading']).toBe('Active Directory');
expect(tabs[2].attributes['heading']).toBe('Standalone');
});
// If the tabs cacheActive is set to true data for all tabs will be fetched at once,
// smb mgr module might hit recursive error when doing multiple request to the db
it('should have cache disabled', () => {
const tabs = fixture.nativeElement.querySelector('cds-tabs');
expect(tabs.cacheActive).toBeFalsy();
});
});

View File

@ -0,0 +1,24 @@
import { Component } from '@angular/core';
enum TABS {
'clusters',
'joinAuths',
'usersgroups'
}
@Component({
selector: 'cd-smb-tabs',
templateUrl: './smb-tabs.component.html',
styleUrls: ['./smb-tabs.component.scss']
})
export class SmbTabsComponent {
selectedTab: TABS;
onSelected(tab: TABS) {
this.selectedTab = tab;
}
public get Tabs(): typeof TABS {
return TABS;
}
}

View File

@ -0,0 +1,19 @@
<cds-tabs
type="contained"
followFocus="true"
isNavigation="true"
[cacheActive]="false"
>
<cds-tab
heading="Users"
i18n-heading>
<cd-table
[data]="selection?.values.users"
columnMode="flex"
[columns]="columns"
selectionType="single"
[hasDetails]="false"
>
</cd-table>
</cds-tab>
</cds-tabs>

View File

@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SmbUsersgroupsDetailsComponent } from './smb-usersgroups-details.component';
describe('SmbUsersgroupsDetailsComponent', () => {
let component: SmbUsersgroupsDetailsComponent;
let fixture: ComponentFixture<SmbUsersgroupsDetailsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [SmbUsersgroupsDetailsComponent]
}).compileComponents();
fixture = TestBed.createComponent(SmbUsersgroupsDetailsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,24 @@
import { Component, Input, OnInit } from '@angular/core';
import { CdTableColumn } from '~/app/shared/models/cd-table-column';
import { SMBUsersGroups } from '../smb.model';
@Component({
selector: 'cd-smb-usersgroups-details',
templateUrl: './smb-usersgroups-details.component.html',
styleUrls: ['./smb-usersgroups-details.component.scss']
})
export class SmbUsersgroupsDetailsComponent implements OnInit {
@Input()
selection: SMBUsersGroups;
columns: CdTableColumn[];
ngOnInit() {
this.columns = [
{
name: $localize`Username`,
prop: 'name',
flexGrow: 2
}
];
}
}

View File

@ -0,0 +1,28 @@
<ng-container *ngIf="usersGroups$ | async as usersGroups">
<cd-table
[data]="usersGroups"
columnMode="flex"
[columns]="columns"
selectionType="single"
[hasDetails]="true"
(setExpandedRow)="setExpandedRow($event)"
(fetchData)="loadUsersGroups()"
>
<cd-smb-usersgroups-details
*cdTableDetail
[selection]="expandedRow"
></cd-smb-usersgroups-details>
</cd-table>
</ng-container>
<ng-template
#groupsNamesTpl
let-row="data.row"
>
<cds-tag
*ngFor="let group of row?.values.groups"
size="md"
>
{{ group.name }}
</cds-tag>
</ng-template>

View File

@ -0,0 +1,27 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SmbUsersgroupsListComponent } from './smb-usersgroups-list.component';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { ToastrModule } from 'ngx-toastr';
import { SharedModule } from '~/app/shared/shared.module';
describe('SmbUsersgroupsListComponent', () => {
let component: SmbUsersgroupsListComponent;
let fixture: ComponentFixture<SmbUsersgroupsListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [SmbUsersgroupsListComponent],
imports: [SharedModule, HttpClientTestingModule, ToastrModule.forRoot(), RouterTestingModule]
}).compileComponents();
fixture = TestBed.createComponent(SmbUsersgroupsListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,83 @@
import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { catchError, switchMap } from 'rxjs/operators';
import { BehaviorSubject, Observable, of } from 'rxjs';
import _ from 'lodash';
import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
import { CdTableAction } from '~/app/shared/models/cd-table-action';
import { CdTableColumn } from '~/app/shared/models/cd-table-column';
import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
import { Permission } from '~/app/shared/models/permissions';
import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
import { SmbService } from '~/app/shared/api/smb.service';
import { SMBUsersGroups } from '../smb.model';
@Component({
selector: 'cd-smb-users-list',
templateUrl: './smb-usersgroups-list.component.html',
styleUrls: ['./smb-usersgroups-list.component.scss']
})
export class SmbUsersgroupsListComponent extends ListWithDetails implements OnInit {
@ViewChild('groupsNamesTpl', { static: true })
groupsNamesTpl: TemplateRef<any>;
columns: CdTableColumn[];
permission: Permission;
tableActions: CdTableAction[];
context: CdTableFetchDataContext;
usersGroups$: Observable<SMBUsersGroups[]>;
subject$ = new BehaviorSubject<SMBUsersGroups[]>([]);
constructor(
private authStorageService: AuthStorageService,
public actionLabels: ActionLabelsI18n,
private smbService: SmbService
) {
super();
this.permission = this.authStorageService.getPermissions().smb;
}
ngOnInit() {
this.columns = [
{
name: $localize`ID`,
prop: 'users_groups_id',
flexGrow: 2
},
{
name: $localize`Number of users`,
prop: 'values.users.length',
flexGrow: 2
},
{
name: $localize`Groups`,
prop: 'values.groups',
cellTemplate: this.groupsNamesTpl,
flexGrow: 2
},
{
name: $localize`Linked to`,
prop: 'values.linked_to_cluster',
flexGrow: 2
}
];
this.usersGroups$ = this.subject$.pipe(
switchMap(() =>
this.smbService.listUsersGroups().pipe(
catchError(() => {
this.context.error();
return of(null);
})
)
)
);
}
loadUsersGroups() {
this.subject$.next([]);
}
}

View File

@ -52,8 +52,6 @@ export const PLACEMENT = {
label: 'label'
};
export const RESOURCE_TYPE = 'ceph.smb.cluster';
export interface SMBShare {
cluster_id: string;
share_id: string;
@ -79,3 +77,42 @@ interface SMBShareLoginControl {
access: 'read' | 'read-write' | 'none' | 'admin';
category?: 'user' | 'group';
}
export interface SMBJoinAuth {
resource_type: string;
auth_id: string;
intent: Intent;
auth: Auth;
linked_to_cluster?: string;
}
export interface SMBUsersGroups {
resource_type: string;
users_groups_id: string;
intent: Intent;
values: Value;
linked_to_cluster?: string;
}
interface Auth {
username: string;
password: string;
}
interface User {
name: string;
password: string;
}
interface Group {
name: string;
}
interface Value {
users: User[];
groups: Group[];
}
type Intent = 'present' | 'removed';
export const CLUSTER_RESOURCE = 'ceph.smb.cluster';

View File

@ -2,7 +2,6 @@ import Close from '@carbon/icons/es/close/32';
import { SmbClusterListComponent } from './smb-cluster-list/smb-cluster-list.component';
import { SmbClusterFormComponent } from './smb-cluster-form/smb-cluster-form.component';
import { AppRoutingModule } from '~/app/app-routing.module';
import { provideCharts, withDefaultRegisterables, BaseChartDirective } from 'ng2-charts';
import { DataTableModule } from '~/app/shared/datatable/datatable.module';
import { SmbDomainSettingModalComponent } from './smb-domain-setting-modal/smb-domain-setting-modal.component';
import { SmbClusterTabsComponent } from './smb-cluster-tabs/smb-cluster-tabs.component';
@ -21,7 +20,8 @@ import {
NumberModule,
PlaceholderModule,
SelectModule,
TabsModule
TabsModule,
TagModule
} from 'carbon-components-angular';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
@ -29,6 +29,11 @@ import { SharedModule } from '~/app/shared/shared.module';
import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
import { SmbUsersgroupsListComponent } from './smb-usersgroups-list/smb-usersgroups-list.component';
import { SmbTabsComponent } from './smb-tabs/smb-tabs.component';
import { SmbJoinAuthListComponent } from './smb-join-auth-list/smb-join-auth-list.component';
import { SmbUsersgroupsDetailsComponent } from './smb-usersgroups-details/smb-usersgroups-details.component';
@NgModule({
imports: [
ReactiveFormsModule,
@ -36,7 +41,6 @@ import { NgModule } from '@angular/core';
CommonModule,
SharedModule,
AppRoutingModule,
BaseChartDirective,
CommonModule,
FormsModule,
ReactiveFormsModule,
@ -44,6 +48,7 @@ import { NgModule } from '@angular/core';
GridModule,
SelectModule,
TabsModule,
TagModule,
InputModule,
CheckboxModule,
SelectModule,
@ -62,9 +67,12 @@ import { NgModule } from '@angular/core';
SmbClusterFormComponent,
SmbDomainSettingModalComponent,
SmbClusterTabsComponent,
SmbShareListComponent
],
providers: [provideCharts(withDefaultRegisterables())]
SmbShareListComponent,
SmbUsersgroupsListComponent,
SmbUsersgroupsDetailsComponent,
SmbTabsComponent,
SmbJoinAuthListComponent
]
})
export class SmbModule {
constructor(private iconService: IconService) {

View File

@ -1,16 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { SmbService } from './smb.service';
import { configureTestBed } from '~/testing/unit-test-helper';
import { provideHttpClient } from '@angular/common/http';
describe('SmbService', () => {
let service: SmbService;
let httpTesting: HttpTestingController;
configureTestBed({
providers: [SmbService],
imports: [HttpClientTestingModule]
providers: [SmbService, provideHttpClient(), provideHttpClientTesting()]
});
beforeEach(() => {
@ -46,4 +46,16 @@ describe('SmbService', () => {
const req = httpTesting.expectOne('api/smb/share?cluster_id=tango');
expect(req.request.method).toBe('GET');
});
it('should call list join auth', () => {
service.listJoinAuths().subscribe();
const req = httpTesting.expectOne('api/smb/joinauth');
expect(req.request.method).toBe('GET');
});
it('should call list usersgroups', () => {
service.listUsersGroups().subscribe();
const req = httpTesting.expectOne('api/smb/usersgroups');
expect(req.request.method).toBe('GET');
});
});

View File

@ -2,7 +2,13 @@ import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { DomainSettings, SMBCluster, SMBShare } from '~/app/ceph/smb/smb.model';
import {
DomainSettings,
SMBCluster,
SMBJoinAuth,
SMBShare,
SMBUsersGroups
} from '~/app/ceph/smb/smb.model';
@Injectable({
providedIn: 'root'
@ -35,4 +41,12 @@ export class SmbService {
listShares(clusterId: string): Observable<SMBShare[]> {
return this.http.get<SMBShare[]>(`${this.baseURL}/share?cluster_id=${clusterId}`);
}
listJoinAuths(): Observable<SMBJoinAuth[]> {
return this.http.get<SMBJoinAuth[]>(`${this.baseURL}/joinauth`);
}
listUsersGroups(): Observable<SMBUsersGroups[]> {
return this.http.get<SMBUsersGroups[]>(`${this.baseURL}/usersgroups`);
}
}

View File

@ -15012,6 +15012,74 @@ paths:
summary: Get an smb cluster
tags:
- SMB
/api/smb/joinauth:
get:
description: "\n List all smb join auth resources\n\n :return:\
\ Returns list of join auth.\n :rtype: List[Dict]\n "
parameters:
- default: ''
in: query
name: join_auth
schema:
type: string
responses:
'200':
content:
application/vnd.ceph.api.v1.0+json:
schema:
items:
properties:
auth:
description: Authentication credentials
properties:
password:
description: Password for authentication
type: string
username:
description: Username for authentication
type: string
required:
- username
- password
type: object
auth_id:
description: Unique identifier for the join auth resource
type: string
intent:
description: Desired state of the resource, e.g., 'present'
or 'removed'
type: string
linked_to_cluster:
description: Optional string containing a cluster ID. If
set, the resource is linked to the cluster and will be automatically
removed when the cluster is removed
type: string
resource_type:
description: ceph.smb.join.auth
type: string
type: object
required:
- resource_type
- auth_id
- intent
- auth
- linked_to_cluster
type: array
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: List smb join authorization resources
tags:
- SMB
/api/smb/share:
get:
description: "\n List all smb shares or all shares for a given cluster\n\
@ -15139,6 +15207,95 @@ paths:
summary: Remove an smb share
tags:
- SMB
/api/smb/usersgroups:
get:
description: "\n List all smb usersgroups resources\n\n :return:\
\ Returns list of usersgroups.\n :rtype: List[Dict]\n "
parameters:
- default: ''
in: query
name: users_groups
schema:
type: string
responses:
'200':
content:
application/vnd.ceph.api.v1.0+json:
schema:
items:
properties:
intent:
description: Desired state of the resource, e.g., 'present'
or 'removed'
type: string
linked_to_cluster:
description: Optional string containing a cluster ID. If
set, the resource is linked to the cluster and will be automatically
removed when the cluster is removed
type: string
resource_type:
description: ceph.smb.usersgroups
type: string
users_groups_id:
description: A short string identifying the usersgroups resource
type: string
values:
description: Required object containing users and groups information
properties:
groups:
description: List of group objects, each containing a name
items:
properties:
name:
description: The name of the group
type: string
required:
- name
type: object
type: array
users:
description: List of user objects, each containing a name
and password
items:
properties:
name:
description: The user name
type: string
password:
description: The password for the user
type: string
required:
- name
- password
type: object
type: array
required:
- users
- groups
type: object
type: object
required:
- resource_type
- users_groups_id
- intent
- values
- linked_to_cluster
type: array
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: List smb user resources
tags:
- SMB
/api/summary:
get:
parameters: []

View File

@ -1,7 +1,7 @@
import json
from unittest.mock import Mock
from dashboard.controllers.smb import SMBCluster, SMBShare
from dashboard.controllers.smb import SMBCluster, SMBJoinAuth, SMBShare, SMBUsersgroups
from .. import mgr
from ..tests import ControllerTestCase
@ -219,3 +219,103 @@ class SMBShareTest(ControllerTestCase):
self._delete(f'{self._endpoint}/smbCluster1/share1')
self.assertStatus(204)
mgr.remote.assert_called_once_with('smb', 'apply_resources', json.dumps(_res_simplified))
class SMBJoinAuthTest(ControllerTestCase):
_endpoint = '/api/smb/joinauth'
_join_auths = {
"resources": [
{
"resource_type": "ceph.smb.join.auth",
"auth_id": "join1-admin",
"intent": "present",
"auth": {
"username": "Administrator",
"password": "Passw0rd"
}
},
{
"resource_type": "ceph.smb.join.auth",
"auth_id": "ja2",
"intent": "present",
"auth": {
"username": "user123",
"password": "foobar"
}
}
]
}
@classmethod
def setup_server(cls):
cls.setup_controllers([SMBJoinAuth])
def test_list_one_join_auth(self):
mgr.remote = Mock(return_value=self._join_auths['resources'][0])
self._get(self._endpoint)
self.assertStatus(200)
self.assertJsonBody([self._join_auths['resources'][0]])
def test_list_multiple_clusters(self):
mgr.remote = Mock(return_value=self._join_auths)
self._get(self._endpoint)
self.assertStatus(200)
self.assertJsonBody(self._join_auths['resources'])
class SMBUsersgroupsTest(ControllerTestCase):
_endpoint = '/api/smb/usersgroups'
_usersgroups = {
"resources": [
{
"resource_type": "ceph.smb.usersgroups",
"users_groups_id": "u2",
"intent": "present",
"values": {
"users": [
{
"name": "user3",
"password": "pass"
}
],
"groups": []
}
},
{
"resource_type": "ceph.smb.usersgroups",
"users_groups_id": "u1",
"intent": "present",
"values": {
"users": [
{
"name": "user2",
"password": "pass"
}
],
"groups": []
}
}
]
}
@classmethod
def setup_server(cls):
cls.setup_controllers([SMBUsersgroups])
def test_list_one_usersgroups(self):
mgr.remote = Mock(return_value=self._usersgroups['resources'][0])
self._get(self._endpoint)
self.assertStatus(200)
self.assertJsonBody([self._usersgroups['resources'][0]])
def test_list_multiple_usersgroups(self):
mgr.remote = Mock(return_value=self._usersgroups)
self._get(self._endpoint)
self.assertStatus(200)
self.assertJsonBody(self._usersgroups['resources'])