]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: add smb service management support 57862/head
authorPedro Gonzalez Gomez <pegonzal@redhat.com>
Mon, 29 Apr 2024 08:48:01 +0000 (10:48 +0200)
committerPedro Gonzalez Gomez <pegonzal@redhat.com>
Tue, 4 Jun 2024 07:26:44 +0000 (09:26 +0200)
Fixes: https://tracker.ceph.com/issues/65681
Signed-off-by: Pedro Gonzalez Gomez <pegonzal@redhat.com>
(cherry picked from commit b831940e29b158d595cb34d2c7eb23ab23f2bb47)

src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/services.po.ts
src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/05-services.e2e-spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/models/service.interface.ts

index c464a3f6cf817d00390aafd96e4666632a97850f..3db483a16a2085126593ebabaffb7e7649ea42ac 100644 (file)
@@ -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');
index 75b46be0f5a5f5b5208b723bf698458828588a5c..0f30542f793c0207b1c01dbf035f09e8b90663e0 100644 (file)
@@ -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');
+    });
   });
 });
index b95f9353db35207b13719b6f52b3c91bf903972f..e7278a09868c70f74bebc77b7140e187dd608004 100644 (file)
@@ -74,7 +74,7 @@
         <div class="form-group row"
              *ngIf="serviceForm.controls.service_type.value !== 'snmp-gateway'">
           <label class="cd-col-form-label"
-                 [ngClass]="{'required': ['mds', 'rgw', 'nfs', 'iscsi', 'ingress'].includes(serviceForm.controls.service_type.value)}"
+                 [ngClass]="{'required': ['mds', 'rgw', 'nfs', 'iscsi', 'smb', 'ingress'].includes(serviceForm.controls.service_type.value)}"
                  for="service_id">
             <span i18n>Id</span>
             <cd-helper i18n>Used in the service name which is &lt;service_type.service_id&gt;</cd-helper>
           </div>
         </ng-container>
 
+        <!-- smb -->
+        <ng-container *ngIf="serviceForm.controls.service_type.value === 'smb'">
+          <div class="form-group row">
+            <label class="cd-col-form-label required"
+                   for="cluster_id"
+                   i18n>
+              Cluster id
+              <cd-helper>
+                <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,
+                   and may not provide actual clustering or availability mechanisms.</span>
+              </cd-helper>
+            </label>
+            <div class="cd-col-form-input">
+              <input id="cluster_id"
+                     class="form-control"
+                     type="text"
+                     formControlName="cluster_id"
+                     placeholder="foo"
+                     i18n-placeholder>
+              <span class="invalid-feedback"
+                    *ngIf="serviceForm.showError('cluster_id', frm, 'required')"
+                    i18n>This field is required.</span>
+            </div>
+          </div>
+
+          <div class="form-group row">
+            <label class="cd-col-form-label required"
+                   for="config_uri">
+              <span i18n>Config URI</span>
+              <cd-helper i18n>
+                Configuration source that should be loaded by the samba-container as the primary configuration file.
+              </cd-helper>
+            </label>
+            <div class="cd-col-form-input">
+              <input id="config_uri"
+                     class="form-control"
+                     type="text"
+                     formControlName="config_uri"
+                     placeholder="rados://.smb/foo/scc.toml"
+                     i18n-placeholder>
+              <span class="invalid-feedback"
+                    *ngIf="serviceForm.showError('config_uri', frm, 'required')"
+                    i18n>This field is required.</span>
+              <span class="invalid-feedback"
+                    *ngIf="serviceForm.showError('config_uri', frm, 'configUriPattern')"
+                    i18n>The value must start with either 'http:', 'https:', 'rados:' or 'rados:mon-config-key:'</span>
+            </div>
+          </div>
+
+          <div class="form-group row"
+               formGroupName="features">
+            <label class="cd-col-form-label"
+                   for="features"
+                   i18n>Features
+              <cd-helper>
+                <span>Pre-defined terms enabling specific deployment characteristics.</span>
+              </cd-helper>
+            </label>
+            <div class="cd-col-form-input">
+              <div class="custom-control custom-checkbox"
+                   *ngFor="let feature of smbFeaturesList">
+                <input class="custom-control-input"
+                       type="checkbox"
+                       name="{{feature}}"
+                       id="{{feature}}"
+                       formControlName="{{feature}}">
+                <label class="custom-control-label"
+                       for="{{feature}}"
+                       i18n>{{feature}}
+                </label>
+              </div>
+            </div>
+          </div>
+
+          <div class="form-group row">
+            <label class="cd-col-form-label"
+                   for="custom_dns">
+              <span i18n>Custom DNS</span>
+              <cd-helper i18n>
+                <span>Comma separated list of DNSs.</span>
+                <br>
+                <span>A list of IP addresses that will be used as the DNS servers for a Samba container.</span>
+              </cd-helper>
+            </label>
+            <div class="cd-col-form-input">
+              <input id="custom_dns"
+                     class="form-control"
+                     type="text"
+                     formControlName="custom_dns"
+                     placeholder="192.168.76.204"
+                     i18n-placeholder>
+            </div>
+          </div>
+
+          <div class="form-group row">
+            <label class="cd-col-form-label"
+                   for="join_sources">
+              <span i18n>Join sources</span>
+              <cd-helper i18n>
+                <span>Comma separated list of URIs.</span>
+                <br>
+                <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>
+              </cd-helper>
+            </label>
+            <div class="cd-col-form-input">
+              <input id="join_sources"
+                     class="form-control"
+                     type="text"
+                     formControlName="join_sources"
+                     placeholder="rados:mon-config-key:smb/config/foo/join1.json"
+                     i18n-placeholder>
+            </div>
+          </div>
+
+          <div class="form-group row">
+            <label class="cd-col-form-label"
+                   for="user_sources">
+              <span i18n>User sources</span>
+              <cd-helper i18n>
+                <span>Comma separated list of URIs.</span>
+                <br>
+                <span>A list of pseudo-uris containing data the samba-container can use to create users (and/or
+                  groups). A ceph based samba container may typically use a rados uri
+                  or a mon config-key store uri </span>
+              </cd-helper>
+            </label>
+            <div class="cd-col-form-input">
+              <input id="user_sources"
+                     class="form-control"
+                     type="text"
+                     formControlName="user_sources"
+                     placeholder="rados:mon-config-key:smb/config/foo/join2.json"
+                     i18n-placeholder>
+            </div>
+          </div>
+
+          <div class="form-group row">
+            <label class="cd-col-form-label"
+                   for="include_ceph_users">
+              <span i18n>Ceph users</span>
+              <cd-helper i18n>
+                <span>Comma separated list of Ceph users.</span>
+                <br>
+                <span>A list of cephx user names that the Samba Containers may use.</span>
+              </cd-helper>
+            </label>
+            <div class="cd-col-form-input">
+              <input id="include_ceph_users"
+                     class="form-control"
+                     type="text"
+                     formControlName="include_ceph_users"
+                     placeholder="client.smb.fs.cluster.foo"
+                     i18n-placeholder>
+            </div>
+          </div>
+
+        </ng-container>
+
         <!-- Ingress -->
         <ng-container *ngIf="serviceForm.controls.service_type.value === 'ingress'">
           <!-- virtual_ip -->
index ebecec5cc3854e31fc36f5b1f61df1b509e01159..4f71abcec7a7d3f2e1db2923e250baddd4103bbf 100644 (file)
@@ -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');
index 564c364426e9a80c37804bfdf78a96bfe0e68d5b..c0f66ed33626f14d8e27728202552827b018fea0 100644 (file)
@@ -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'];
index 177382c5350595624a60c3b82b8b24089bfee315..77b2dfde591e741b72b8682719d4ce7e405403d1 100644 (file)
@@ -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 {