]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: fix HAProxy (now called ingress)
authorAvan Thakkar <athakkar@localhost.localdomain>
Tue, 20 Apr 2021 12:19:23 +0000 (17:49 +0530)
committerSage Weil <sage@newdream.net>
Fri, 7 May 2021 12:40:37 +0000 (07:40 -0500)
Fixes: https://tracker.ceph.com/issues/50319
Signed-off-by: Avan Thakkar <athakkar@redhat.com>
Support from Dashboard UI to create Ingress service type.

(cherry picked from commit 21318e8fa965c352e00d84c04ee072dd9fe45e4f)

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/python-common/ceph/deployment/service_spec.py

index e2d4f01140ccef8c1032f8546215de28a9c653b5..4774299b78d93a54aa8937bee945314b4f96a341 100644 (file)
           </div>
         </div>
 
+        <!-- backend_service -->
+          <div *ngIf="serviceForm.controls.service_type.value === 'ingress'"
+               class="form-group row">
+            <label i18n
+                   class="cd-col-form-label"
+                   [ngClass]="{'required': ['ingress'].includes(serviceForm.controls.service_type.value)}"
+                   for="backend_service">Backend Service</label>
+            <div class="cd-col-form-input">
+              <select id="backend_service"
+                      name="backend_service"
+                      class="form-control custom-select"
+                      formControlName="backend_service">
+                <option *ngIf="services === null"
+                        [ngValue]="null"
+                        i18n>Loading...</option>
+                <option *ngIf="services !== null && services.length === 0"
+                        [ngValue]="null"
+                        i18n>-- No service available --</option>
+                <option *ngIf="services !== null && services.length > 0"
+                        [ngValue]="null"
+                        i18n>-- Select an existing RGW service --</option>
+                <option *ngFor="let service of services"
+                        [value]="service.service_name">{{ service.service_name }}</option>
+              </select>
+              <span class="invalid-feedback"
+                    *ngIf="serviceForm.showError('backend_service', frm, 'required')"
+                    i18n>This field is required.</span>
+            </div>
+          </div>
+
         <!-- Service id -->
         <div class="form-group row">
           <label i18n
                  class="cd-col-form-label"
-                 [ngClass]="{'required': ['mds', 'rgw', 'nfs', 'iscsi'].includes(serviceForm.controls.service_type.value)}"
+                 [ngClass]="{'required': ['mds', 'rgw', 'nfs', 'iscsi', 'ingress'].includes(serviceForm.controls.service_type.value)}"
                  for="service_id">Id</label>
           <div class="cd-col-form-input">
             <input id="service_id"
           </div>
         </ng-container>
 
-        <!-- RGW & iSCSI -->
-        <ng-container *ngIf="!serviceForm.controls.unmanaged.value && ['rgw', 'iscsi'].includes(serviceForm.controls.service_type.value)">
+        <!-- Ingress -->
+        <ng-container *ngIf="!serviceForm.controls.unmanaged.value && serviceForm.controls.service_type.value === 'ingress'">
+          <!-- virtual_ip -->
+          <div class="form-group row">
+            <label class="cd-col-form-label"
+                   [ngClass]="{'required': ['ingress'].includes(serviceForm.controls.service_type.value)}"
+                   for="virtual_ip">
+              <span i18n>Virtual IP</span>
+              <cd-helper>
+                <span i18n>The virtual IP address and subnet (in CIDR notation) where the ingress service will be available.</span>
+              </cd-helper>
+            </label>
+            <div class="cd-col-form-input">
+              <input id="virtual_ip"
+                     class="form-control"
+                     type="text"
+                     formControlName="virtual_ip">
+              <span class="invalid-feedback"
+                    *ngIf="serviceForm.showError('virtual_ip', frm, 'required')"
+                    i18n>This field is required.</span>
+            </div>
+          </div>
+
+          <!-- frontend_port -->
+          <div class="form-group row">
+            <label class="cd-col-form-label"
+                   [ngClass]="{'required': ['ingress'].includes(serviceForm.controls.service_type.value)}"
+                   for="frontend_port">
+              <span i18n>Frontend Port</span>
+              <cd-helper>
+                <span i18n>The port used to access the ingress service.</span>
+              </cd-helper>
+            </label>
+            <div class="cd-col-form-input">
+              <input id="frontend_port"
+                     class="form-control"
+                     type="number"
+                     formControlName="frontend_port"
+                     min="1"
+                     max="65535">
+              <span class="invalid-feedback"
+                    *ngIf="serviceForm.showError('frontend_port', frm, 'pattern')"
+                    i18n>The entered value needs to be a number.</span>
+              <span class="invalid-feedback"
+                    *ngIf="serviceForm.showError('frontend_port', frm, 'min')"
+                    i18n>The value must be at least 1.</span>
+              <span class="invalid-feedback"
+                    *ngIf="serviceForm.showError('frontend_port', frm, 'max')"
+                    i18n>The value cannot exceed 65535.</span>
+              <span class="invalid-feedback"
+                    *ngIf="serviceForm.showError('frontend_port', frm, 'required')"
+                    i18n>This field is required.</span>
+            </div>
+          </div>
+
+          <!-- monitor_port -->
+          <div class="form-group row">
+            <label class="cd-col-form-label"
+                   [ngClass]="{'required': ['ingress'].includes(serviceForm.controls.service_type.value)}"
+                   for="monitor_port">
+              <span i18n>Monitor Port</span>
+              <cd-helper>
+                <span i18n>The port used by haproxy for load balancer status.</span>
+              </cd-helper>
+            </label>
+            <div class="cd-col-form-input">
+              <input id="monitor_port"
+                     class="form-control"
+                     type="number"
+                     formControlName="monitor_port"
+                     min="1"
+                     max="65535">
+              <span class="invalid-feedback"
+                    *ngIf="serviceForm.showError('monitor_port', frm, 'pattern')"
+                    i18n>The entered value needs to be a number.</span>
+              <span class="invalid-feedback"
+                    *ngIf="serviceForm.showError('monitor_port', frm, 'min')"
+                    i18n>The value must be at least 1.</span>
+              <span class="invalid-feedback"
+                    *ngIf="serviceForm.showError('monitor_port', frm, 'max')"
+                    i18n>The value cannot exceed 65535.</span>
+              <span class="invalid-feedback"
+                    *ngIf="serviceForm.showError('monitor_port', frm, 'required')"
+                    i18n>This field is required.</span>
+            </div>
+          </div>
+          <!-- virtual_interface_networks -->
+          <div class="form-group row">
+            <label class="cd-col-form-label"
+                   for="virtual_interface_networks">
+              <span i18n>CIDR Networks</span>
+              <cd-helper>
+                <span i18n>A list of networks to identify which network interface to use for the virtual IP address.</span>
+              </cd-helper>
+            </label>
+            <div class="cd-col-form-input">
+              <input id="virtual_interface_networks"
+                     class="form-control"
+                     type="text"
+                     formControlName="virtual_interface_networks">
+            </div>
+          </div>
+        </ng-container>
+        <!-- RGW, Ingress & iSCSI -->
+        <ng-container *ngIf="!serviceForm.controls.unmanaged.value && ['rgw', 'iscsi', 'ingress'].includes(serviceForm.controls.service_type.value)">
           <!-- ssl -->
           <div class="form-group row">
             <div class="cd-col-form-offset">
index 6ed8b414532ab22050f8f93a6a56746732f71672..5b7fc6cac43b0e3ae3224604d1b60c003ec199be 100644 (file)
@@ -331,5 +331,68 @@ describe('ServiceFormComponent', () => {
         formHelper.expectError('api_port', 'pattern');
       });
     });
+
+    describe('should test service ingress', () => {
+      beforeEach(() => {
+        formHelper.setValue('service_type', 'ingress');
+        formHelper.setValue('service_id', 'rgw.foo');
+        formHelper.setValue('backend_service', 'rgw.foo');
+        formHelper.setValue('virtual_ip', '192.168.20.1/24');
+        formHelper.setValue('ssl', false);
+      });
+
+      it('should submit ingress', () => {
+        component.onSubmit();
+        expect(cephServiceService.create).toHaveBeenCalledWith({
+          service_type: 'ingress',
+          placement: {},
+          unmanaged: false,
+          backend_service: 'rgw.foo',
+          service_id: 'rgw.foo',
+          virtual_ip: '192.168.20.1/24',
+          virtual_interface_networks: null,
+          ssl: false
+        });
+      });
+
+      it('should submit valid frontend and monitor port', () => {
+        // min value
+        formHelper.setValue('frontend_port', 1);
+        formHelper.setValue('monitor_port', 1);
+        component.onSubmit();
+        formHelper.expectValid('frontend_port');
+        formHelper.expectValid('monitor_port');
+
+        // max value
+        formHelper.setValue('frontend_port', 65535);
+        formHelper.setValue('monitor_port', 65535);
+        component.onSubmit();
+        formHelper.expectValid('frontend_port');
+        formHelper.expectValid('monitor_port');
+      });
+
+      it('should submit invalid frontend and monitor port', () => {
+        // min
+        formHelper.setValue('frontend_port', 0);
+        formHelper.setValue('monitor_port', 0);
+        component.onSubmit();
+        formHelper.expectError('frontend_port', 'min');
+        formHelper.expectError('monitor_port', 'min');
+
+        // max
+        formHelper.setValue('frontend_port', 65536);
+        formHelper.setValue('monitor_port', 65536);
+        component.onSubmit();
+        formHelper.expectError('frontend_port', 'max');
+        formHelper.expectError('monitor_port', 'max');
+
+        // pattern
+        formHelper.setValue('frontend_port', 'abc');
+        formHelper.setValue('monitor_port', 'abc');
+        component.onSubmit();
+        formHelper.expectError('frontend_port', 'pattern');
+        formHelper.expectError('monitor_port', 'pattern');
+      });
+    });
   });
 });
index 533f2ae833a7552f8edc5fba65bfdc2a4a7e2da3..aee978849a7b6eb2d91fd2c70c91c29c83410573 100644 (file)
@@ -18,6 +18,7 @@ import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
 import { CdValidators } from '~/app/shared/forms/cd-validators';
 import { FinishedTask } from '~/app/shared/models/finished-task';
+import { CephServiceSpec } from '~/app/shared/models/service.interface';
 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
 
 @Component({
@@ -38,6 +39,7 @@ export class ServiceFormComponent extends CdForm implements OnInit {
   labelClick = new Subject<string>();
   labelFocus = new Subject<string>();
   pools: Array<object>;
+  services: Array<CephServiceSpec> = [];
 
   constructor(
     public actionLabels: ActionLabelsI18n,
@@ -76,6 +78,9 @@ export class ServiceFormComponent extends CdForm implements OnInit {
           CdValidators.requiredIf({
             service_type: 'iscsi'
           }),
+          CdValidators.requiredIf({
+            service_type: 'ingress'
+          }),
           CdValidators.composeIf(
             {
               service_type: 'rgw'
@@ -147,7 +152,29 @@ export class ServiceFormComponent extends CdForm implements OnInit {
           })
         ]
       ],
-      // RGW & iSCSI
+      // Ingress
+      backend_service: [
+        null,
+        [
+          CdValidators.requiredIf({
+            service_type: 'ingress',
+            unmanaged: false
+          })
+        ]
+      ],
+      virtual_ip: [
+        null,
+        [
+          CdValidators.requiredIf({
+            service_type: 'ingress',
+            unmanaged: false
+          })
+        ]
+      ],
+      frontend_port: [null, [CdValidators.number(false), Validators.min(1), Validators.max(65535)]],
+      monitor_port: [null, [CdValidators.number(false), Validators.min(1), Validators.max(65535)]],
+      virtual_interface_networks: [null],
+      // RGW, Ingress & iSCSI
       ssl: [false],
       ssl_cert: [
         '',
@@ -218,6 +245,9 @@ export class ServiceFormComponent extends CdForm implements OnInit {
     this.poolService.getList().subscribe((resp: Array<object>) => {
       this.pools = resp;
     });
+    this.cephServiceService.list().subscribe((services: CephServiceSpec[]) => {
+      this.services = services.filter((service: any) => service.service_type === 'rgw');
+    });
   }
 
   goToListView() {
@@ -313,6 +343,24 @@ export class ServiceFormComponent extends CdForm implements OnInit {
             serviceSpec['ssl_key'] = values['ssl_key'].trim();
           }
           break;
+        case 'ingress':
+          serviceSpec['backend_service'] = values['backend_service'];
+          if (_.isString(values['virtual_ip']) && !_.isEmpty(values['virtual_ip'])) {
+            serviceSpec['virtual_ip'] = values['virtual_ip'].trim();
+          }
+          if (_.isNumber(values['frontend_port']) && values['frontend_port'] > 0) {
+            serviceSpec['frontend_port'] = values['frontend_port'];
+          }
+          if (_.isNumber(values['monitor_port']) && values['monitor_port'] > 0) {
+            serviceSpec['monitor_port'] = values['monitor_port'];
+          }
+          serviceSpec['ssl'] = values['ssl'];
+          if (values['ssl']) {
+            serviceSpec['ssl_cert'] = values['ssl_cert'].trim();
+            serviceSpec['ssl_key'] = values['ssl_key'].trim();
+          }
+          serviceSpec['virtual_interface_networks'] = values['virtual_interface_networks'];
+          break;
       }
     }
     this.taskWrapperService
index 5a21071649c3da980f70b58d9a620ee0d215401b..3e2915dbb0cbb9855ccb11d246329b0e9ed41d50 100644 (file)
@@ -879,6 +879,7 @@ class IngressSpec(ServiceSpec):
                  backend_service: Optional[str] = None,
                  frontend_port: Optional[int] = None,
                  ssl_cert: Optional[str] = None,
+                 ssl_key: Optional[str] = None,
                  ssl_dh_param: Optional[str] = None,
                  ssl_ciphers: Optional[List[str]] = None,
                  ssl_options: Optional[List[str]] = None,
@@ -891,6 +892,8 @@ class IngressSpec(ServiceSpec):
                  virtual_interface_networks: Optional[List[str]] = [],
                  haproxy_container_image: Optional[str] = None,
                  keepalived_container_image: Optional[str] = None,
+                 unmanaged: bool = False,
+                 ssl: bool = False
                  ):
         assert service_type == 'ingress'
         super(IngressSpec, self).__init__(
@@ -901,6 +904,7 @@ class IngressSpec(ServiceSpec):
         self.backend_service = backend_service
         self.frontend_port = frontend_port
         self.ssl_cert = ssl_cert
+        self.ssl_key = ssl_key
         self.ssl_dh_param = ssl_dh_param
         self.ssl_ciphers = ssl_ciphers
         self.ssl_options = ssl_options
@@ -912,6 +916,8 @@ class IngressSpec(ServiceSpec):
         self.virtual_interface_networks = virtual_interface_networks or []
         self.haproxy_container_image = haproxy_container_image
         self.keepalived_container_image = keepalived_container_image
+        self.unmanaged = unmanaged
+        self.ssl = ssl
 
     def get_port_start(self) -> List[int]:
         return [cast(int, self.frontend_port),