]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/shared/classes/crush.node.selection.class.ts
bump version to 15.2.4-pve1
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / shared / classes / crush.node.selection.class.ts
1 import { AbstractControl } from '@angular/forms';
2
3 import * as _ from 'lodash';
4
5 import { CrushNode } from '../models/crush-node';
6
7 export class CrushNodeSelectionClass {
8 private nodes: CrushNode[] = [];
9 private easyNodes: { [id: number]: CrushNode } = {};
10 private allDevices: string[] = [];
11 private controls: {
12 root: AbstractControl;
13 failure: AbstractControl;
14 device: AbstractControl;
15 };
16
17 buckets: CrushNode[] = [];
18 failureDomains: { [type: string]: CrushNode[] } = {};
19 failureDomainKeys: string[] = [];
20 devices: string[] = [];
21 deviceCount = 0;
22
23 initCrushNodeSelection(
24 nodes: CrushNode[],
25 rootControl: AbstractControl,
26 failureControl: AbstractControl,
27 deviceControl: AbstractControl
28 ) {
29 this.nodes = nodes;
30 nodes.forEach((node) => {
31 this.easyNodes[node.id] = node;
32 });
33 this.buckets = _.sortBy(
34 nodes.filter((n) => n.children),
35 'name'
36 );
37 this.controls = {
38 root: rootControl,
39 failure: failureControl,
40 device: deviceControl
41 };
42 this.preSelectRoot();
43 this.controls.root.valueChanges.subscribe(() => this.onRootChange());
44 this.controls.failure.valueChanges.subscribe(() => this.onFailureDomainChange());
45 this.controls.device.valueChanges.subscribe(() => this.onDeviceChange());
46 }
47
48 private preSelectRoot() {
49 const rootNode = this.nodes.find((node) => node.type === 'root');
50 this.silentSet(this.controls.root, rootNode);
51 this.onRootChange();
52 }
53
54 private silentSet(control: AbstractControl, value: any) {
55 control.setValue(value, { emitEvent: false });
56 }
57
58 private onRootChange() {
59 const nodes = this.getSubNodes(this.controls.root.value);
60 const domains = {};
61 nodes.forEach((node) => {
62 if (!domains[node.type]) {
63 domains[node.type] = [];
64 }
65 domains[node.type].push(node);
66 });
67 Object.keys(domains).forEach((type) => {
68 if (domains[type].length <= 1) {
69 delete domains[type];
70 }
71 });
72 this.failureDomains = domains;
73 this.failureDomainKeys = Object.keys(domains).sort();
74 this.updateFailureDomain();
75 }
76
77 private getSubNodes(node: CrushNode): CrushNode[] {
78 let subNodes = [node]; // Includes parent node
79 if (!node.children) {
80 return subNodes;
81 }
82 node.children.forEach((id) => {
83 const childNode = this.easyNodes[id];
84 subNodes = subNodes.concat(this.getSubNodes(childNode));
85 });
86 return subNodes;
87 }
88
89 private updateFailureDomain() {
90 let failureDomain = this.getIncludedCustomValue(
91 this.controls.failure,
92 Object.keys(this.failureDomains)
93 );
94 if (failureDomain === '') {
95 failureDomain = this.setMostCommonDomain(this.controls.failure);
96 }
97 this.updateDevices(failureDomain);
98 }
99
100 private getIncludedCustomValue(control: AbstractControl, includedIn: string[]) {
101 return control.dirty && includedIn.includes(control.value) ? control.value : '';
102 }
103
104 private setMostCommonDomain(failureControl: AbstractControl): string {
105 let winner = { n: 0, type: '' };
106 Object.keys(this.failureDomains).forEach((type) => {
107 const n = this.failureDomains[type].length;
108 if (winner.n < n) {
109 winner = { n, type };
110 }
111 });
112 this.silentSet(failureControl, winner.type);
113 return winner.type;
114 }
115
116 private onFailureDomainChange() {
117 this.updateDevices();
118 }
119
120 private updateDevices(failureDomain: string = this.controls.failure.value) {
121 const subNodes = _.flatten(
122 this.failureDomains[failureDomain].map((node) => this.getSubNodes(node))
123 );
124 this.allDevices = subNodes.filter((n) => n.device_class).map((n) => n.device_class);
125 this.devices = _.uniq(this.allDevices).sort();
126 const device =
127 this.devices.length === 1
128 ? this.devices[0]
129 : this.getIncludedCustomValue(this.controls.device, this.devices);
130 this.silentSet(this.controls.device, device);
131 this.onDeviceChange(device);
132 }
133
134 private onDeviceChange(deviceType: string = this.controls.device.value) {
135 this.deviceCount =
136 deviceType === ''
137 ? this.allDevices.length
138 : this.allDevices.filter((type) => type === deviceType).length;
139 }
140 }