From b831940e29b158d595cb34d2c7eb23ab23f2bb47 Mon Sep 17 00:00:00 2001 From: Pedro Gonzalez Gomez Date: Mon, 29 Apr 2024 10:48:01 +0200 Subject: [PATCH] mgr/dashboard: add smb service management support Fixes: https://tracker.ceph.com/issues/65681 Signed-off-by: Pedro Gonzalez Gomez --- .../cypress/e2e/cluster/services.po.ts | 7 + .../e2e/orchestrator/05-services.e2e-spec.ts | 9 + .../service-form/service-form.component.html | 160 +++++++++++++++++- .../service-form.component.spec.ts | 21 +++ .../service-form/service-form.component.ts | 81 ++++++++- .../app/shared/models/service.interface.ts | 6 + 6 files changed, 282 insertions(+), 2 deletions(-) diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/services.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/services.po.ts index c464a3f6cf8..3db483a16a2 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/services.po.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/services.po.ts @@ -68,6 +68,13 @@ export class ServicesPageHelper extends PageHelper { unmanaged ? cy.get('label[for=unmanaged]').click() : cy.get('#count').type(String(count)); break; + case 'smb': + cy.get('#service_id').type('testsmb'); + unmanaged ? cy.get('label[for=unmanaged]').click() : cy.get('#count').type(String(count)); + cy.get('#cluster_id').type('cluster_foo'); + cy.get('#config_uri').type('rados://.smb/foo/scc.toml'); + break; + case 'snmp-gateway': this.selectOption('snmp_version', snmpVersion); cy.get('#snmp_destination').type('192.168.0.1:8443'); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/05-services.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/05-services.e2e-spec.ts index 75b46be0f5a..0f30542f793 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/05-services.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/05-services.e2e-spec.ts @@ -31,5 +31,14 @@ describe('Services page', () => { services.deleteService('ingress.rgw.foo'); }); + + it('should create and delete a smb service', () => { + services.navigateTo('create'); + services.addService('smb'); + + services.checkExist('smb.testsmb', true); + + services.deleteService('smb.testsmb'); + }); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.html index b95f9353db3..e7278a09868 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.html @@ -74,7 +74,7 @@
+ + +
+ +
+ + This field is required. +
+
+ +
+ +
+ + This field is required. + The value must start with either 'http:', 'https:', 'rados:' or 'rados:mon-config-key:' +
+
+ +
+ +
+
+ + +
+
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.spec.ts index ebecec5cc38..4f71abcec7a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.spec.ts @@ -387,6 +387,27 @@ x4Ea7kGVgx9kWh5XjWz9wjZvY49UKIT5ppIAWPMbLl3UpfckiuNhTA== }); }); + describe('should test service smb', () => { + beforeEach(() => { + formHelper.setValue('service_type', 'smb'); + formHelper.setValue('service_id', 'foo'); + formHelper.setValue('cluster_id', 'cluster_foo'); + formHelper.setValue('config_uri', 'rados://.smb/foo/scc.toml'); + }); + + it('should submit smb', () => { + component.onSubmit(); + expect(cephServiceService.create).toHaveBeenCalledWith({ + service_type: 'smb', + placement: {}, + unmanaged: false, + service_id: 'foo', + cluster_id: 'cluster_foo', + config_uri: 'rados://.smb/foo/scc.toml' + }); + }); + }); + describe('should test service ingress', () => { beforeEach(() => { formHelper.setValue('service_type', 'ingress'); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.ts index 564c364426e..c0f66ed3362 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.ts @@ -1,6 +1,6 @@ import { HttpParams } from '@angular/common/http'; import { Component, Input, OnInit, ViewChild } from '@angular/core'; -import { AbstractControl, Validators } from '@angular/forms'; +import { AbstractControl, UntypedFormControl, Validators } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { NgbActiveModal, NgbModalRef, NgbTypeahead } from '@ng-bootstrap/ng-bootstrap'; @@ -47,6 +47,7 @@ export class ServiceFormComponent extends CdForm implements OnInit { readonly SNMP_DESTINATION_PATTERN = /^[^\:]+:[0-9]/; readonly SNMP_ENGINE_ID_PATTERN = /^[0-9A-Fa-f]{10,64}/g; readonly INGRESS_SUPPORTED_SERVICE_TYPES = ['rgw', 'nfs']; + readonly SMB_CONFIG_URI_PATTERN = /^(http:|https:|rados:|rados:mon-config-key:)/; @ViewChild(NgbTypeahead, { static: false }) typeahead: NgbTypeahead; @@ -85,6 +86,7 @@ export class ServiceFormComponent extends CdForm implements OnInit { realmNames: string[]; zonegroupNames: string[]; zoneNames: string[]; + smbFeaturesList = ['domain']; constructor( public actionLabels: ActionLabelsI18n, @@ -146,6 +148,9 @@ export class ServiceFormComponent extends CdForm implements OnInit { CdValidators.requiredIf({ service_type: 'ingress' }), + CdValidators.requiredIf({ + service_type: 'smb' + }), CdValidators.composeIf( { service_type: 'rgw' @@ -205,6 +210,44 @@ export class ServiceFormComponent extends CdForm implements OnInit { }) ] ], + // smb + cluster_id: [ + null, + [ + CdValidators.requiredIf({ + service_type: 'smb' + }) + ] + ], + features: new CdFormGroup( + this.smbFeaturesList.reduce((acc: object, e) => { + acc[e] = new UntypedFormControl(false); + return acc; + }, {}) + ), + config_uri: [ + null, + [ + CdValidators.composeIf( + { + service_type: 'smb' + }, + [ + Validators.required, + CdValidators.custom('configUriPattern', (value: string) => { + if (_.isEmpty(value)) { + return false; + } + return !this.SMB_CONFIG_URI_PATTERN.test(value); + }) + ] + ) + ] + ], + custom_dns: [null], + join_sources: [null], + user_sources: [null], + include_ceph_users: [null], // Ingress backend_service: [ null, @@ -486,6 +529,28 @@ export class ServiceFormComponent extends CdForm implements OnInit { this.serviceForm.get('ssl_key').setValue(response[0].spec?.ssl_key); } break; + case 'smb': + const smbSpecKeys = [ + 'cluster_id', + 'config_uri', + 'features', + 'join_sources', + 'user_sources', + 'custom_dns', + 'include_ceph_users' + ]; + smbSpecKeys.forEach((key) => { + if (key === 'features') { + if (response[0].spec?.features) { + response[0].spec.features.forEach((feature) => { + this.serviceForm.get(`features.${feature}`).setValue(true); + }); + } + } else { + this.serviceForm.get(key).setValue(response[0].spec[key]); + } + }); + break; case 'snmp-gateway': const snmpCommonSpecKeys = ['snmp_version', 'snmp_destination']; snmpCommonSpecKeys.forEach((key) => { @@ -753,6 +818,20 @@ export class ServiceFormComponent extends CdForm implements OnInit { serviceSpec['pool'] = values['pool']; break; + case 'smb': + serviceSpec['cluster_id'] = values['cluster_id']?.trim(); + serviceSpec['config_uri'] = values['config_uri']?.trim(); + for (const feature in values['features']) { + if (values['features'][feature]) { + (serviceSpec['features'] = serviceSpec['features'] || []).push(feature); + } + } + serviceSpec['custom_dns'] = values['custom_dns']?.trim(); + serviceSpec['join_sources'] = values['join_sources']?.trim(); + serviceSpec['user_sources'] = values['user_sources']?.trim(); + serviceSpec['include_ceph_users'] = values['include_ceph_users']?.trim(); + break; + case 'snmp-gateway': serviceSpec['credentials'] = {}; serviceSpec['snmp_version'] = values['snmp_version']; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/service.interface.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/service.interface.ts index 177382c5350..77b2dfde591 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/service.interface.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/service.interface.ts @@ -40,6 +40,12 @@ export interface CephServiceAdditionalSpec { rgw_realm: string; rgw_zonegroup: string; rgw_zone: string; + cluster_id: string; + features: string[]; + config_uri: string; + custom_dns: string[]; + join_sources: string[]; + include_ceph_users: string[]; } export interface CephServicePlacement { -- 2.39.5