]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: display placement column in service table 38209/head
authorVolker Theile <vtheile@suse.com>
Thu, 5 Nov 2020 15:12:51 +0000 (16:12 +0100)
committerVolker Theile <vtheile@suse.com>
Mon, 23 Nov 2020 09:28:30 +0000 (10:28 +0100)
Fixes: https://tracker.ceph.com/issues/44404
Signed-off-by: Volker Theile <vtheile@suse.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/placement.pipe.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/placement.pipe.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/services.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/services.component.ts

index 62464cadefe947f276e45cebd8a040898d4abf55..916f831f8bf3792b1f98d5251712bf690e14eff6 100644 (file)
@@ -47,6 +47,7 @@ import { RulesListComponent } from './prometheus/rules-list/rules-list.component
 import { SilenceFormComponent } from './prometheus/silence-form/silence-form.component';
 import { SilenceListComponent } from './prometheus/silence-list/silence-list.component';
 import { SilenceMatcherModalComponent } from './prometheus/silence-matcher-modal/silence-matcher-modal.component';
+import { PlacementPipe } from './services/placement.pipe';
 import { ServiceDaemonListComponent } from './services/service-daemon-list/service-daemon-list.component';
 import { ServiceDetailsComponent } from './services/service-details/service-details.component';
 import { ServiceFormComponent } from './services/service-form/service-form.component';
@@ -108,7 +109,8 @@ import { TelemetryComponent } from './telemetry/telemetry.component';
     TelemetryComponent,
     PrometheusTabsComponent,
     ServiceFormComponent,
-    OsdFlagsIndivModalComponent
+    OsdFlagsIndivModalComponent,
+    PlacementPipe
   ]
 })
 export class ClusterModule {}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/placement.pipe.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/placement.pipe.spec.ts
new file mode 100644 (file)
index 0000000..6aef3c3
--- /dev/null
@@ -0,0 +1,78 @@
+import { PlacementPipe } from './placement.pipe';
+
+describe('PlacementPipe', () => {
+  const pipe = new PlacementPipe();
+
+  it('create an instance', () => {
+    expect(pipe).toBeTruthy();
+  });
+
+  it('transforms to no spec', () => {
+    expect(pipe.transform(undefined)).toBe('no spec');
+  });
+
+  it('transforms to unmanaged', () => {
+    expect(pipe.transform({ unmanaged: true })).toBe('unmanaged');
+  });
+
+  it('transforms placement (1)', () => {
+    expect(
+      pipe.transform({
+        placement: {
+          hosts: ['mon0']
+        }
+      })
+    ).toBe('mon0');
+  });
+
+  it('transforms placement (2)', () => {
+    expect(
+      pipe.transform({
+        placement: {
+          hosts: ['mon0', 'mgr0']
+        }
+      })
+    ).toBe('mon0;mgr0');
+  });
+
+  it('transforms placement (3)', () => {
+    expect(
+      pipe.transform({
+        placement: {
+          count: 1
+        }
+      })
+    ).toBe('count:1');
+  });
+
+  it('transforms placement (4)', () => {
+    expect(
+      pipe.transform({
+        placement: {
+          label: 'foo'
+        }
+      })
+    ).toBe('label:foo');
+  });
+
+  it('transforms placement (5)', () => {
+    expect(
+      pipe.transform({
+        placement: {
+          host_pattern: '*'
+        }
+      })
+    ).toBe('*');
+  });
+
+  it('transforms placement (6)', () => {
+    expect(
+      pipe.transform({
+        placement: {
+          count: 2,
+          hosts: ['mon0', 'mgr0']
+        }
+      })
+    ).toBe('mon0;mgr0;count:2');
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/placement.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/placement.pipe.ts
new file mode 100644 (file)
index 0000000..bd461bc
--- /dev/null
@@ -0,0 +1,41 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+import _ from 'lodash';
+
+@Pipe({
+  name: 'placement'
+})
+export class PlacementPipe implements PipeTransform {
+  /**
+   * Convert the placement configuration into human readable form.
+   * The output is equal to the column 'PLACEMENT' in 'ceph orch ls'.
+   * @param serviceSpec The service specification to process.
+   * @return The placement configuration as human readable string.
+   */
+  transform(serviceSpec: object | undefined): string {
+    if (_.isUndefined(serviceSpec)) {
+      return $localize`no spec`;
+    }
+    if (_.get(serviceSpec, 'unmanaged', false)) {
+      return $localize`unmanaged`;
+    }
+    const kv: Array<any> = [];
+    const hosts: Array<string> = _.get(serviceSpec, 'placement.hosts');
+    const count: number = _.get(serviceSpec, 'placement.count');
+    const label: string = _.get(serviceSpec, 'placement.label');
+    const hostPattern: string = _.get(serviceSpec, 'placement.host_pattern');
+    if (_.isArray(hosts)) {
+      kv.push(...hosts);
+    }
+    if (_.isNumber(count)) {
+      kv.push($localize`count:${count}`);
+    }
+    if (_.isString(label)) {
+      kv.push($localize`label:${label}`);
+    }
+    if (_.isString(hostPattern)) {
+      kv.push(...hostPattern);
+    }
+    return kv.join(';');
+  }
+}
index c3631d50e530460432c8088b5b5083df7efd950d..a6e56ce2282d7a44555b19d71739c7809c834547 100644 (file)
@@ -82,7 +82,10 @@ describe('ServicesComponent', () => {
   it('should have columns that are sortable', () => {
     expect(
       component.columns
+        // Filter the 'Expand/Collapse Row' column.
         .filter((column) => !(column.cellClass === 'cd-datatable-expand-collapse'))
+        // Filter the 'Placement' column.
+        .filter((column) => !(column.prop === ''))
         .every((column) => Boolean(column.prop))
     ).toBeTruthy();
   });
index 8306f7ddf0fdc808d275e8975b4966386d6b60f5..db4f51c56d56fcdb48984c711eda1dd66caeab52 100644 (file)
@@ -24,6 +24,7 @@ import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
 import { ModalService } from '~/app/shared/services/modal.service';
 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
 import { URLBuilderService } from '~/app/shared/services/url-builder.service';
+import { PlacementPipe } from './placement.pipe';
 
 const BASE_URL = 'services';
 
@@ -108,6 +109,12 @@ export class ServicesComponent extends ListWithDetails implements OnChanges, OnI
           length: 12
         }
       },
+      {
+        name: $localize`Placement`,
+        prop: '',
+        pipe: new PlacementPipe(),
+        flexGrow: 1
+      },
       {
         name: $localize`Running`,
         prop: 'status.running',