Skip to content

Commit b831940

Browse files
committed
mgr/dashboard: add smb service management support
Fixes: https://tracker.ceph.com/issues/65681 Signed-off-by: Pedro Gonzalez Gomez <[email protected]>
1 parent 0e36423 commit b831940

File tree

6 files changed

+282
-2
lines changed

6 files changed

+282
-2
lines changed

src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/services.po.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ export class ServicesPageHelper extends PageHelper {
6868
unmanaged ? cy.get('label[for=unmanaged]').click() : cy.get('#count').type(String(count));
6969
break;
7070

71+
case 'smb':
72+
cy.get('#service_id').type('testsmb');
73+
unmanaged ? cy.get('label[for=unmanaged]').click() : cy.get('#count').type(String(count));
74+
cy.get('#cluster_id').type('cluster_foo');
75+
cy.get('#config_uri').type('rados://.smb/foo/scc.toml');
76+
break;
77+
7178
case 'snmp-gateway':
7279
this.selectOption('snmp_version', snmpVersion);
7380
cy.get('#snmp_destination').type('192.168.0.1:8443');

src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/05-services.e2e-spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,14 @@ describe('Services page', () => {
3131

3232
services.deleteService('ingress.rgw.foo');
3333
});
34+
35+
it('should create and delete a smb service', () => {
36+
services.navigateTo('create');
37+
services.addService('smb');
38+
39+
services.checkExist('smb.testsmb', true);
40+
41+
services.deleteService('smb.testsmb');
42+
});
3443
});
3544
});

src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.html

Lines changed: 159 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
<div class="form-group row"
7575
*ngIf="serviceForm.controls.service_type.value !== 'snmp-gateway'">
7676
<label class="cd-col-form-label"
77-
[ngClass]="{'required': ['mds', 'rgw', 'nfs', 'iscsi', 'ingress'].includes(serviceForm.controls.service_type.value)}"
77+
[ngClass]="{'required': ['mds', 'rgw', 'nfs', 'iscsi', 'smb', 'ingress'].includes(serviceForm.controls.service_type.value)}"
7878
for="service_id">
7979
<span i18n>Id</span>
8080
<cd-helper i18n>Used in the service name which is &lt;service_type.service_id&gt;</cd-helper>
@@ -395,6 +395,164 @@
395395
</div>
396396
</ng-container>
397397

398+
<!-- smb -->
399+
<ng-container *ngIf="serviceForm.controls.service_type.value === 'smb'">
400+
<div class="form-group row">
401+
<label class="cd-col-form-label required"
402+
for="cluster_id"
403+
i18n>
404+
Cluster id
405+
<cd-helper>
406+
<span>A short name identifying the SMB “cluster”. In this case a cluster is simply a management unit of one or more Samba services sharing a common configuration,
407+
and may not provide actual clustering or availability mechanisms.</span>
408+
</cd-helper>
409+
</label>
410+
<div class="cd-col-form-input">
411+
<input id="cluster_id"
412+
class="form-control"
413+
type="text"
414+
formControlName="cluster_id"
415+
placeholder="foo"
416+
i18n-placeholder>
417+
<span class="invalid-feedback"
418+
*ngIf="serviceForm.showError('cluster_id', frm, 'required')"
419+
i18n>This field is required.</span>
420+
</div>
421+
</div>
422+
423+
<div class="form-group row">
424+
<label class="cd-col-form-label required"
425+
for="config_uri">
426+
<span i18n>Config URI</span>
427+
<cd-helper i18n>
428+
Configuration source that should be loaded by the samba-container as the primary configuration file.
429+
</cd-helper>
430+
</label>
431+
<div class="cd-col-form-input">
432+
<input id="config_uri"
433+
class="form-control"
434+
type="text"
435+
formControlName="config_uri"
436+
placeholder="rados://.smb/foo/scc.toml"
437+
i18n-placeholder>
438+
<span class="invalid-feedback"
439+
*ngIf="serviceForm.showError('config_uri', frm, 'required')"
440+
i18n>This field is required.</span>
441+
<span class="invalid-feedback"
442+
*ngIf="serviceForm.showError('config_uri', frm, 'configUriPattern')"
443+
i18n>The value must start with either 'http:', 'https:', 'rados:' or 'rados:mon-config-key:'</span>
444+
</div>
445+
</div>
446+
447+
<div class="form-group row"
448+
formGroupName="features">
449+
<label class="cd-col-form-label"
450+
for="features"
451+
i18n>Features
452+
<cd-helper>
453+
<span>Pre-defined terms enabling specific deployment characteristics.</span>
454+
</cd-helper>
455+
</label>
456+
<div class="cd-col-form-input">
457+
<div class="custom-control custom-checkbox"
458+
*ngFor="let feature of smbFeaturesList">
459+
<input class="custom-control-input"
460+
type="checkbox"
461+
name="{{feature}}"
462+
id="{{feature}}"
463+
formControlName="{{feature}}">
464+
<label class="custom-control-label"
465+
for="{{feature}}"
466+
i18n>{{feature}}
467+
</label>
468+
</div>
469+
</div>
470+
</div>
471+
472+
<div class="form-group row">
473+
<label class="cd-col-form-label"
474+
for="custom_dns">
475+
<span i18n>Custom DNS</span>
476+
<cd-helper i18n>
477+
<span>Comma separated list of DNSs.</span>
478+
<br>
479+
<span>A list of IP addresses that will be used as the DNS servers for a Samba container.</span>
480+
</cd-helper>
481+
</label>
482+
<div class="cd-col-form-input">
483+
<input id="custom_dns"
484+
class="form-control"
485+
type="text"
486+
formControlName="custom_dns"
487+
placeholder="192.168.76.204"
488+
i18n-placeholder>
489+
</div>
490+
</div>
491+
492+
<div class="form-group row">
493+
<label class="cd-col-form-label"
494+
for="join_sources">
495+
<span i18n>Join sources</span>
496+
<cd-helper i18n>
497+
<span>Comma separated list of URIs.</span>
498+
<br>
499+
<span>A list of values that will be used to identify where authentication data that will be used to perform domain joins are located.</span>
500+
</cd-helper>
501+
</label>
502+
<div class="cd-col-form-input">
503+
<input id="join_sources"
504+
class="form-control"
505+
type="text"
506+
formControlName="join_sources"
507+
placeholder="rados:mon-config-key:smb/config/foo/join1.json"
508+
i18n-placeholder>
509+
</div>
510+
</div>
511+
512+
<div class="form-group row">
513+
<label class="cd-col-form-label"
514+
for="user_sources">
515+
<span i18n>User sources</span>
516+
<cd-helper i18n>
517+
<span>Comma separated list of URIs.</span>
518+
<br>
519+
<span>A list of pseudo-uris containing data the samba-container can use to create users (and/or
520+
groups). A ceph based samba container may typically use a rados uri
521+
or a mon config-key store uri </span>
522+
</cd-helper>
523+
</label>
524+
<div class="cd-col-form-input">
525+
<input id="user_sources"
526+
class="form-control"
527+
type="text"
528+
formControlName="user_sources"
529+
placeholder="rados:mon-config-key:smb/config/foo/join2.json"
530+
i18n-placeholder>
531+
</div>
532+
</div>
533+
534+
<div class="form-group row">
535+
<label class="cd-col-form-label"
536+
for="include_ceph_users">
537+
<span i18n>Ceph users</span>
538+
<cd-helper i18n>
539+
<span>Comma separated list of Ceph users.</span>
540+
<br>
541+
<span>A list of cephx user names that the Samba Containers may use.</span>
542+
</cd-helper>
543+
</label>
544+
<div class="cd-col-form-input">
545+
<input id="include_ceph_users"
546+
class="form-control"
547+
type="text"
548+
formControlName="include_ceph_users"
549+
placeholder="client.smb.fs.cluster.foo"
550+
i18n-placeholder>
551+
</div>
552+
</div>
553+
554+
</ng-container>
555+
398556
<!-- Ingress -->
399557
<ng-container *ngIf="serviceForm.controls.service_type.value === 'ingress'">
400558
<!-- virtual_ip -->

src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.spec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,27 @@ x4Ea7kGVgx9kWh5XjWz9wjZvY49UKIT5ppIAWPMbLl3UpfckiuNhTA==
387387
});
388388
});
389389

390+
describe('should test service smb', () => {
391+
beforeEach(() => {
392+
formHelper.setValue('service_type', 'smb');
393+
formHelper.setValue('service_id', 'foo');
394+
formHelper.setValue('cluster_id', 'cluster_foo');
395+
formHelper.setValue('config_uri', 'rados://.smb/foo/scc.toml');
396+
});
397+
398+
it('should submit smb', () => {
399+
component.onSubmit();
400+
expect(cephServiceService.create).toHaveBeenCalledWith({
401+
service_type: 'smb',
402+
placement: {},
403+
unmanaged: false,
404+
service_id: 'foo',
405+
cluster_id: 'cluster_foo',
406+
config_uri: 'rados://.smb/foo/scc.toml'
407+
});
408+
});
409+
});
410+
390411
describe('should test service ingress', () => {
391412
beforeEach(() => {
392413
formHelper.setValue('service_type', 'ingress');

src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.ts

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { HttpParams } from '@angular/common/http';
22
import { Component, Input, OnInit, ViewChild } from '@angular/core';
3-
import { AbstractControl, Validators } from '@angular/forms';
3+
import { AbstractControl, UntypedFormControl, Validators } from '@angular/forms';
44
import { ActivatedRoute, Router } from '@angular/router';
55

66
import { NgbActiveModal, NgbModalRef, NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
@@ -47,6 +47,7 @@ export class ServiceFormComponent extends CdForm implements OnInit {
4747
readonly SNMP_DESTINATION_PATTERN = /^[^\:]+:[0-9]/;
4848
readonly SNMP_ENGINE_ID_PATTERN = /^[0-9A-Fa-f]{10,64}/g;
4949
readonly INGRESS_SUPPORTED_SERVICE_TYPES = ['rgw', 'nfs'];
50+
readonly SMB_CONFIG_URI_PATTERN = /^(http:|https:|rados:|rados:mon-config-key:)/;
5051
@ViewChild(NgbTypeahead, { static: false })
5152
typeahead: NgbTypeahead;
5253

@@ -85,6 +86,7 @@ export class ServiceFormComponent extends CdForm implements OnInit {
8586
realmNames: string[];
8687
zonegroupNames: string[];
8788
zoneNames: string[];
89+
smbFeaturesList = ['domain'];
8890

8991
constructor(
9092
public actionLabels: ActionLabelsI18n,
@@ -146,6 +148,9 @@ export class ServiceFormComponent extends CdForm implements OnInit {
146148
CdValidators.requiredIf({
147149
service_type: 'ingress'
148150
}),
151+
CdValidators.requiredIf({
152+
service_type: 'smb'
153+
}),
149154
CdValidators.composeIf(
150155
{
151156
service_type: 'rgw'
@@ -205,6 +210,44 @@ export class ServiceFormComponent extends CdForm implements OnInit {
205210
})
206211
]
207212
],
213+
// smb
214+
cluster_id: [
215+
null,
216+
[
217+
CdValidators.requiredIf({
218+
service_type: 'smb'
219+
})
220+
]
221+
],
222+
features: new CdFormGroup(
223+
this.smbFeaturesList.reduce((acc: object, e) => {
224+
acc[e] = new UntypedFormControl(false);
225+
return acc;
226+
}, {})
227+
),
228+
config_uri: [
229+
null,
230+
[
231+
CdValidators.composeIf(
232+
{
233+
service_type: 'smb'
234+
},
235+
[
236+
Validators.required,
237+
CdValidators.custom('configUriPattern', (value: string) => {
238+
if (_.isEmpty(value)) {
239+
return false;
240+
}
241+
return !this.SMB_CONFIG_URI_PATTERN.test(value);
242+
})
243+
]
244+
)
245+
]
246+
],
247+
custom_dns: [null],
248+
join_sources: [null],
249+
user_sources: [null],
250+
include_ceph_users: [null],
208251
// Ingress
209252
backend_service: [
210253
null,
@@ -486,6 +529,28 @@ export class ServiceFormComponent extends CdForm implements OnInit {
486529
this.serviceForm.get('ssl_key').setValue(response[0].spec?.ssl_key);
487530
}
488531
break;
532+
case 'smb':
533+
const smbSpecKeys = [
534+
'cluster_id',
535+
'config_uri',
536+
'features',
537+
'join_sources',
538+
'user_sources',
539+
'custom_dns',
540+
'include_ceph_users'
541+
];
542+
smbSpecKeys.forEach((key) => {
543+
if (key === 'features') {
544+
if (response[0].spec?.features) {
545+
response[0].spec.features.forEach((feature) => {
546+
this.serviceForm.get(`features.${feature}`).setValue(true);
547+
});
548+
}
549+
} else {
550+
this.serviceForm.get(key).setValue(response[0].spec[key]);
551+
}
552+
});
553+
break;
489554
case 'snmp-gateway':
490555
const snmpCommonSpecKeys = ['snmp_version', 'snmp_destination'];
491556
snmpCommonSpecKeys.forEach((key) => {
@@ -753,6 +818,20 @@ export class ServiceFormComponent extends CdForm implements OnInit {
753818
serviceSpec['pool'] = values['pool'];
754819
break;
755820

821+
case 'smb':
822+
serviceSpec['cluster_id'] = values['cluster_id']?.trim();
823+
serviceSpec['config_uri'] = values['config_uri']?.trim();
824+
for (const feature in values['features']) {
825+
if (values['features'][feature]) {
826+
(serviceSpec['features'] = serviceSpec['features'] || []).push(feature);
827+
}
828+
}
829+
serviceSpec['custom_dns'] = values['custom_dns']?.trim();
830+
serviceSpec['join_sources'] = values['join_sources']?.trim();
831+
serviceSpec['user_sources'] = values['user_sources']?.trim();
832+
serviceSpec['include_ceph_users'] = values['include_ceph_users']?.trim();
833+
break;
834+
756835
case 'snmp-gateway':
757836
serviceSpec['credentials'] = {};
758837
serviceSpec['snmp_version'] = values['snmp_version'];

src/pybind/mgr/dashboard/frontend/src/app/shared/models/service.interface.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ export interface CephServiceAdditionalSpec {
4040
rgw_realm: string;
4141
rgw_zonegroup: string;
4242
rgw_zone: string;
43+
cluster_id: string;
44+
features: string[];
45+
config_uri: string;
46+
custom_dns: string[];
47+
join_sources: string[];
48+
include_ceph_users: string[];
4349
}
4450

4551
export interface CephServicePlacement {

0 commit comments

Comments
 (0)