mirror of
https://github.com/ceph/ceph
synced 2024-12-29 15:03:33 +00:00
Merge pull request #43584 from rhcs-dashboard/add-multiple-hosts-at-once
mgr/dashboard: Cluster Creation Add multiple hosts at once Reviewed-by: Alfonso Martínez <almartin@redhat.com> Reviewed-by: Avan Thakkar <athakkar@redhat.com> Reviewed-by: Ernesto Puerta <epuertat@redhat.com> Reviewed-by: Nizamudeen A <nia@redhat.com> Reviewed-by: Pere Diaz Bou <pdiazbou@redhat.com>
This commit is contained in:
commit
006b78e3b0
@ -9,12 +9,15 @@ describe('Create cluster add host page', () => {
|
||||
const hostnames = [
|
||||
'ceph-node-00.cephlab.com',
|
||||
'ceph-node-01.cephlab.com',
|
||||
'ceph-node-02.cephlab.com'
|
||||
'ceph-node-02.cephlab.com',
|
||||
'ceph-node-[01-02].cephlab.com'
|
||||
];
|
||||
const addHost = (hostname: string, exist?: boolean) => {
|
||||
const addHost = (hostname: string, exist?: boolean, pattern?: boolean) => {
|
||||
cy.get('.btn.btn-accent').first().click({ force: true });
|
||||
createClusterHostPage.add(hostname, exist, false);
|
||||
createClusterHostPage.checkExist(hostname, true);
|
||||
if (!pattern) {
|
||||
createClusterHostPage.checkExist(hostname, true);
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@ -35,6 +38,9 @@ describe('Create cluster add host page', () => {
|
||||
|
||||
addHost(hostnames[1], false);
|
||||
addHost(hostnames[2], false);
|
||||
createClusterHostPage.delete(hostnames[1]);
|
||||
createClusterHostPage.delete(hostnames[2]);
|
||||
addHost(hostnames[3], false, true);
|
||||
});
|
||||
|
||||
it('should delete a host and add it back', () => {
|
||||
|
17
src/pybind/mgr/dashboard/frontend/package-lock.json
generated
17
src/pybind/mgr/dashboard/frontend/package-lock.json
generated
@ -3802,6 +3802,12 @@
|
||||
"@babel/types": "^7.3.0"
|
||||
}
|
||||
},
|
||||
"@types/brace-expansion": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/brace-expansion/-/brace-expansion-1.1.0.tgz",
|
||||
"integrity": "sha512-SaU/Kgp6z40CiF9JxlsrSrBEa+8YIry9IiCPhhYSNekeEhIAkY7iyu9aZ+5dSQIdo7mf86MUVvxWYm5GAzB/0g==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/chart.js": {
|
||||
"version": "2.9.34",
|
||||
"resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.34.tgz",
|
||||
@ -9503,11 +9509,6 @@
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
},
|
||||
"fsevents": {
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"version": "2.1.3"
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
@ -15277,8 +15278,6 @@
|
||||
}
|
||||
},
|
||||
"fsevents": {
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"version": "2.1.3"
|
||||
},
|
||||
"glob-parent": {
|
||||
@ -24662,8 +24661,6 @@
|
||||
}
|
||||
},
|
||||
"fsevents": {
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"version": "2.1.3"
|
||||
},
|
||||
"glob-parent": {
|
||||
@ -25494,8 +25491,6 @@
|
||||
}
|
||||
},
|
||||
"fsevents": {
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"version": "2.1.3"
|
||||
},
|
||||
"glob-parent": {
|
||||
|
@ -114,6 +114,7 @@
|
||||
"@angular/language-service": "11.2.14",
|
||||
"@applitools/eyes-cypress": "^3.22.0",
|
||||
"@compodoc/compodoc": "1.1.11",
|
||||
"@types/brace-expansion": "^1.1.0",
|
||||
"@types/jest": "26.0.14",
|
||||
"@types/lodash": "4.14.161",
|
||||
"@types/node": "12.12.62",
|
||||
|
@ -16,8 +16,17 @@
|
||||
<!-- Hostname -->
|
||||
<div class="form-group row">
|
||||
<label class="cd-col-form-label required"
|
||||
for="hostname"
|
||||
i18n>Hostname</label>
|
||||
for="hostname">
|
||||
<ng-container i18n>Hostname</ng-container>
|
||||
<cd-helper>
|
||||
<p i18n>To add multiple hosts at once, you can enter:</p>
|
||||
<ul>
|
||||
<li i18n>a comma-separated list of hostnames <samp>(e.g.: example-01,example-02,example-03)</samp>,</li>
|
||||
<li i18n>a range expression <samp>(e.g.: example-[01-03].ceph)</samp>,</li>
|
||||
<li i18n>a comma separated range expression <samp>(e.g.: example-[01-05].lab.com,example2-[1-4].lab.com,example3-[001-006].lab.com)</samp></li>
|
||||
</ul>
|
||||
</cd-helper>
|
||||
</label>
|
||||
<div class="cd-col-form-input">
|
||||
<input class="form-control"
|
||||
type="text"
|
||||
@ -25,7 +34,8 @@
|
||||
id="hostname"
|
||||
name="hostname"
|
||||
formControlName="hostname"
|
||||
autofocus>
|
||||
autofocus
|
||||
(keyup)="checkHostNameValue()">
|
||||
<span class="invalid-feedback"
|
||||
*ngIf="hostForm.showError('hostname', formDir, 'required')"
|
||||
i18n>This field is required.</span>
|
||||
@ -36,7 +46,8 @@
|
||||
</div>
|
||||
|
||||
<!-- Address -->
|
||||
<div class="form-group row">
|
||||
<div class="form-group row"
|
||||
*ngIf="!hostPattern">
|
||||
<label class="cd-col-form-label"
|
||||
for="addr"
|
||||
i18n>Nework address</label>
|
||||
|
@ -81,4 +81,88 @@ describe('HostFormComponent', () => {
|
||||
component.submit();
|
||||
expect(component.status).toBe('maintenance');
|
||||
});
|
||||
|
||||
it('should expand the hostname correctly', () => {
|
||||
component.hostForm.get('hostname').setValue('ceph-node-00.cephlab.com');
|
||||
fixture.detectChanges();
|
||||
component.submit();
|
||||
expect(component.hostnameArray).toStrictEqual(['ceph-node-00.cephlab.com']);
|
||||
|
||||
component.hostnameArray = [];
|
||||
|
||||
component.hostForm.get('hostname').setValue('ceph-node-[00-10].cephlab.com');
|
||||
fixture.detectChanges();
|
||||
component.submit();
|
||||
expect(component.hostnameArray).toStrictEqual([
|
||||
'ceph-node-00.cephlab.com',
|
||||
'ceph-node-01.cephlab.com',
|
||||
'ceph-node-02.cephlab.com',
|
||||
'ceph-node-03.cephlab.com',
|
||||
'ceph-node-04.cephlab.com',
|
||||
'ceph-node-05.cephlab.com',
|
||||
'ceph-node-06.cephlab.com',
|
||||
'ceph-node-07.cephlab.com',
|
||||
'ceph-node-08.cephlab.com',
|
||||
'ceph-node-09.cephlab.com',
|
||||
'ceph-node-10.cephlab.com'
|
||||
]);
|
||||
|
||||
component.hostnameArray = [];
|
||||
|
||||
component.hostForm.get('hostname').setValue('ceph-node-00.cephlab.com,ceph-node-1.cephlab.com');
|
||||
fixture.detectChanges();
|
||||
component.submit();
|
||||
expect(component.hostnameArray).toStrictEqual([
|
||||
'ceph-node-00.cephlab.com',
|
||||
'ceph-node-1.cephlab.com'
|
||||
]);
|
||||
|
||||
component.hostnameArray = [];
|
||||
|
||||
component.hostForm
|
||||
.get('hostname')
|
||||
.setValue('ceph-mon-[01-05].lab.com,ceph-osd-[1-4].lab.com,ceph-rgw-[001-006].lab.com');
|
||||
fixture.detectChanges();
|
||||
component.submit();
|
||||
expect(component.hostnameArray).toStrictEqual([
|
||||
'ceph-mon-01.lab.com',
|
||||
'ceph-mon-02.lab.com',
|
||||
'ceph-mon-03.lab.com',
|
||||
'ceph-mon-04.lab.com',
|
||||
'ceph-mon-05.lab.com',
|
||||
'ceph-osd-1.lab.com',
|
||||
'ceph-osd-2.lab.com',
|
||||
'ceph-osd-3.lab.com',
|
||||
'ceph-osd-4.lab.com',
|
||||
'ceph-rgw-001.lab.com',
|
||||
'ceph-rgw-002.lab.com',
|
||||
'ceph-rgw-003.lab.com',
|
||||
'ceph-rgw-004.lab.com',
|
||||
'ceph-rgw-005.lab.com',
|
||||
'ceph-rgw-006.lab.com'
|
||||
]);
|
||||
|
||||
component.hostnameArray = [];
|
||||
|
||||
component.hostForm
|
||||
.get('hostname')
|
||||
.setValue('ceph-(mon-[00-04],osd-[001-005],rgw-[1-3]).lab.com');
|
||||
fixture.detectChanges();
|
||||
component.submit();
|
||||
expect(component.hostnameArray).toStrictEqual([
|
||||
'ceph-mon-00.lab.com',
|
||||
'ceph-mon-01.lab.com',
|
||||
'ceph-mon-02.lab.com',
|
||||
'ceph-mon-03.lab.com',
|
||||
'ceph-mon-04.lab.com',
|
||||
'ceph-osd-001.lab.com',
|
||||
'ceph-osd-002.lab.com',
|
||||
'ceph-osd-003.lab.com',
|
||||
'ceph-osd-004.lab.com',
|
||||
'ceph-osd-005.lab.com',
|
||||
'ceph-rgw-1.lab.com',
|
||||
'ceph-rgw-2.lab.com',
|
||||
'ceph-rgw-3.lab.com'
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -3,6 +3,7 @@ import { FormControl, Validators } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import expand from 'brace-expansion';
|
||||
|
||||
import { HostService } from '~/app/shared/api/host.service';
|
||||
import { SelectMessages } from '~/app/shared/components/select/select-messages.model';
|
||||
@ -23,10 +24,12 @@ export class HostFormComponent extends CdForm implements OnInit {
|
||||
action: string;
|
||||
resource: string;
|
||||
hostnames: string[];
|
||||
hostnameArray: string[] = [];
|
||||
addr: string;
|
||||
status: string;
|
||||
allLabels: string[];
|
||||
pageURL: string;
|
||||
hostPattern = false;
|
||||
|
||||
messages = new SelectMessages({
|
||||
empty: $localize`There are no labels.`,
|
||||
@ -59,6 +62,12 @@ export class HostFormComponent extends CdForm implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
// check if hostname is a single value or pattern to hide network address field
|
||||
checkHostNameValue() {
|
||||
const hostNames = this.hostForm.get('hostname').value;
|
||||
hostNames.match(/[()\[\]{},]/g) ? (this.hostPattern = true) : (this.hostPattern = false);
|
||||
}
|
||||
|
||||
private createForm() {
|
||||
this.hostForm = new CdFormGroup({
|
||||
hostname: new FormControl('', {
|
||||
@ -77,30 +86,77 @@ export class HostFormComponent extends CdForm implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
private isCommaSeparatedPattern(hostname: string) {
|
||||
// eg. ceph-node-01.cephlab.com,ceph-node-02.cephlab.com
|
||||
return hostname.includes(',');
|
||||
}
|
||||
|
||||
private isRangeTypePattern(hostname: string) {
|
||||
// check if it is a range expression or comma separated range expression
|
||||
// eg. ceph-mon-[01-05].lab.com,ceph-osd-[02-08].lab.com,ceph-rgw-[01-09]
|
||||
return hostname.includes('[') && hostname.includes(']') && !hostname.match(/(?![^(]*\)),/g);
|
||||
}
|
||||
|
||||
private replaceBraces(hostname: string) {
|
||||
// pattern to replace range [0-5] to [0..5](valid expression for brace expansion)
|
||||
// replace any kind of brackets with curly braces
|
||||
return hostname
|
||||
.replace(/(?<=\d)\s*-\s*(?=\d)/g, '..')
|
||||
.replace(/\(/g, '{')
|
||||
.replace(/\)/g, '}')
|
||||
.replace(/\[/g, '{')
|
||||
.replace(/]/g, '}');
|
||||
}
|
||||
|
||||
// expand hostnames in case hostname is a pattern
|
||||
private checkHostNamePattern(hostname: string) {
|
||||
if (this.isRangeTypePattern(hostname)) {
|
||||
const hostnameRange = this.replaceBraces(hostname);
|
||||
this.hostnameArray = expand(hostnameRange);
|
||||
} else if (this.isCommaSeparatedPattern(hostname)) {
|
||||
let hostArray = [];
|
||||
hostArray = hostname.split(',');
|
||||
hostArray.forEach((host: string) => {
|
||||
if (this.isRangeTypePattern(host)) {
|
||||
const hostnameRange = this.replaceBraces(host);
|
||||
this.hostnameArray = this.hostnameArray.concat(expand(hostnameRange));
|
||||
} else {
|
||||
this.hostnameArray.push(host);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// single hostname
|
||||
this.hostnameArray.push(hostname);
|
||||
}
|
||||
}
|
||||
|
||||
submit() {
|
||||
const hostname = this.hostForm.get('hostname').value;
|
||||
this.checkHostNamePattern(hostname);
|
||||
this.addr = this.hostForm.get('addr').value;
|
||||
this.status = this.hostForm.get('maintenance').value ? 'maintenance' : '';
|
||||
this.allLabels = this.hostForm.get('labels').value;
|
||||
if (this.pageURL !== 'hosts' && !this.allLabels.includes('_no_schedule')) {
|
||||
this.allLabels.push('_no_schedule');
|
||||
}
|
||||
this.taskWrapper
|
||||
.wrapTaskAroundCall({
|
||||
task: new FinishedTask('host/' + URLVerbs.ADD, {
|
||||
hostname: hostname
|
||||
}),
|
||||
call: this.hostService.create(hostname, this.addr, this.allLabels, this.status)
|
||||
})
|
||||
.subscribe({
|
||||
error: () => {
|
||||
this.hostForm.setErrors({ cdSubmitButton: true });
|
||||
},
|
||||
complete: () => {
|
||||
this.pageURL === 'hosts'
|
||||
? this.router.navigate([this.pageURL, { outlets: { modal: null } }])
|
||||
: this.activeModal.close();
|
||||
}
|
||||
});
|
||||
this.hostnameArray.forEach((hostName: string) => {
|
||||
this.taskWrapper
|
||||
.wrapTaskAroundCall({
|
||||
task: new FinishedTask('host/' + URLVerbs.ADD, {
|
||||
hostname: hostName
|
||||
}),
|
||||
call: this.hostService.create(hostName, this.addr, this.allLabels, this.status)
|
||||
})
|
||||
.subscribe({
|
||||
error: () => {
|
||||
this.hostForm.setErrors({ cdSubmitButton: true });
|
||||
},
|
||||
complete: () => {
|
||||
this.pageURL === 'hosts'
|
||||
? this.router.navigate([this.pageURL, { outlets: { modal: null } }])
|
||||
: this.activeModal.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user