Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NAS-130487 / 25.04 / Switch WebUI to use new system-wide iSCSI Discovery Auth APIs #11051

Merged
merged 3 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/app/interfaces/iscsi.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import {

export interface IscsiPortal {
comment: string;
discovery_authgroup: number;
discovery_authmethod: IscsiAuthMethod;
id: number;
listen: IscsiInterface[];
tag: number;
Expand Down Expand Up @@ -33,6 +31,7 @@ export interface IscsiAuthAccess {
secret: string;
tag: number;
user: string;
discovery_auth: IscsiAuthMethod;
}

export type IscsiAuthAccessUpdate = Omit<IscsiAuthAccess, 'id'>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,22 @@
<mat-card>
<mat-card-content>
<form class="ix-form-container" [formGroup]="form" (submit)="onSubmit()">
<ix-fieldset [title]="'Group' | translate">
<ix-fieldset [title]="'Authentication Method and Group' | translate">
<ix-input
formControlName="tag"
type="number"
[label]="'Group ID' | translate"
[tooltip]="tooltips.tag | translate"
[required]="true"
></ix-input>

<ix-select
formControlName="discovery_auth"
[label]="'Discovery Authentication' | translate"
[tooltip]="tooltips.discovery_auth | translate"
[options]="discoveryAuthOptions$"
[required]="true"
></ix-select>
</ix-fieldset>

<ix-fieldset [title]="'User' | translate">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ describe('AuthorizedAccessFormComponent', () => {
secret: '123456789012',
peeruser: 'new-peer',
peersecret: 'peer123456789012',
discovery_auth: 'NONE',
}]);
expect(spectator.inject(SlideInRef).close).toHaveBeenCalled();
});
Expand All @@ -95,18 +96,20 @@ describe('AuthorizedAccessFormComponent', () => {
const values = await form.getValues();
expect(values).toEqual({
'Group ID': '23',
'Discovery Authentication': 'NONE',
User: 'user',
Secret: '123456789012',
'Secret (Confirm)': '',
'Secret (Confirm)': '123456789012',
'Peer User': 'peer',
'Peer Secret': 'peer123456789012',
'Peer Secret (Confirm)': '',
'Peer Secret (Confirm)': 'peer123456789012',
});
});

it('edits existing authorized access when form opened for edit is submitted', async () => {
await form.fillForm({
'Group ID': '120',
'Discovery Authentication': 'NONE',
User: 'updated-user',
Secret: '123456789012',
'Secret (Confirm)': '123456789012',
Expand All @@ -128,6 +131,7 @@ describe('AuthorizedAccessFormComponent', () => {
secret: '123456789012',
peeruser: '',
peersecret: '',
discovery_auth: 'NONE',
},
],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ import { MatButton } from '@angular/material/button';
import { MatCard, MatCardContent } from '@angular/material/card';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { TranslateService, TranslateModule } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import {
debounceTime, merge, Observable, of,
} from 'rxjs';
import { RequiresRolesDirective } from 'app/directives/requires-roles/requires-roles.directive';
import { IscsiAuthMethod } from 'app/enums/iscsi.enum';
import { Role } from 'app/enums/role.enum';
import { helptextSharingIscsi } from 'app/helptext/sharing';
import { IscsiAuthAccess, IscsiAuthAccessUpdate } from 'app/interfaces/iscsi.interface';
import { IscsiAuthAccess } from 'app/interfaces/iscsi.interface';
import { Option } from 'app/interfaces/option.interface';
import { FormActionsComponent } from 'app/modules/forms/ix-forms/components/form-actions/form-actions.component';
import { IxFieldsetComponent } from 'app/modules/forms/ix-forms/components/ix-fieldset/ix-fieldset.component';
import { IxInputComponent } from 'app/modules/forms/ix-forms/components/ix-input/ix-input.component';
import { IxSelectComponent } from 'app/modules/forms/ix-forms/components/ix-select/ix-select.component';
import { FormErrorHandlerService } from 'app/modules/forms/ix-forms/services/form-error-handler.service';
import { IxValidatorsService } from 'app/modules/forms/ix-forms/services/ix-validators.service';
import {
Expand All @@ -39,6 +44,7 @@ import { WebSocketService } from 'app/services/ws.service';
ReactiveFormsModule,
IxFieldsetComponent,
IxInputComponent,
IxSelectComponent,
FormActionsComponent,
RequiresRolesDirective,
MatButton,
Expand Down Expand Up @@ -76,6 +82,7 @@ export class AuthorizedAccessFormComponent implements OnInit {
Validators.maxLength(16),
]],
peersecret_confirm: [''],
discovery_auth: [IscsiAuthMethod.None],
}, {
validators: [
matchOthersFgValidator(
Expand All @@ -97,13 +104,26 @@ export class AuthorizedAccessFormComponent implements OnInit {
});

isLoading = false;
discoveryAuthOptions$: Observable<Option<IscsiAuthMethod>[]>;

readonly defaultDiscoveryAuthOptions = [
{
label: 'NONE',
value: IscsiAuthMethod.None,
},
{
label: 'CHAP',
value: IscsiAuthMethod.Chap,
},
];

readonly tooltips = {
tag: helptextSharingIscsi.authaccess_tooltip_tag,
user: helptextSharingIscsi.authaccess_tooltip_user,
secret: helptextSharingIscsi.authaccess_tooltip_user,
peeruser: helptextSharingIscsi.authaccess_tooltip_peeruser,
peersecret: helptextSharingIscsi.authaccess_tooltip_peersecret,
discovery_auth: helptextSharingIscsi.portal_form_tooltip_discovery_authmethod,
};

readonly requiredRoles = [
Expand All @@ -124,6 +144,31 @@ export class AuthorizedAccessFormComponent implements OnInit {
) {}

ngOnInit(): void {
this.discoveryAuthOptions$ = of(this.defaultDiscoveryAuthOptions);

merge(
this.form.controls.peeruser.valueChanges,
this.form.controls.peersecret.valueChanges,
).pipe(debounceTime(300), untilDestroyed(this)).subscribe(() => {
if (this.form.value.peeruser && this.form.value.peersecret) {
this.discoveryAuthOptions$ = of([
...this.defaultDiscoveryAuthOptions,
{
label: 'Mutual CHAP',
value: IscsiAuthMethod.ChapMutual,
},
]);
if (this.form.value.discovery_auth === IscsiAuthMethod.ChapMutual) {
this.form.controls.discovery_auth.setValue(IscsiAuthMethod.ChapMutual);
}
} else {
this.discoveryAuthOptions$ = of(this.defaultDiscoveryAuthOptions);
if (this.form.value.discovery_auth === IscsiAuthMethod.ChapMutual) {
this.form.controls.discovery_auth.setValue(IscsiAuthMethod.None);
}
}
});

if (this.editingAccess) {
this.setAccessForEdit();
}
Expand All @@ -134,24 +179,28 @@ export class AuthorizedAccessFormComponent implements OnInit {
}

setAccessForEdit(): void {
this.form.patchValue(this.editingAccess);
this.form.patchValue({
...this.editingAccess,
secret_confirm: this.editingAccess.secret,
peersecret_confirm: this.editingAccess.peersecret,
});
}

onSubmit(): void {
const values = this.form.value;
delete values.secret_confirm;
delete values.peersecret_confirm;
const payload = {
tag: values.tag,
user: values.user,
secret: values.secret,
peeruser: values.peeruser,
peersecret: values.peersecret,
discovery_auth: values.discovery_auth,
};

this.isLoading = true;
let request$: Observable<unknown>;
if (this.isNew) {
request$ = this.ws.call('iscsi.auth.create', [values as IscsiAuthAccessUpdate]);
} else {
request$ = this.ws.call('iscsi.auth.update', [
this.editingAccess.id,
values as IscsiAuthAccessUpdate,
]);
}
const request$ = this.isNew
? this.ws.call('iscsi.auth.create', [payload])
: this.ws.call('iscsi.auth.update', [this.editingAccess.id, payload]);

request$.pipe(untilDestroyed(this)).subscribe({
next: () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,26 +117,20 @@ describe('IscsiWizardComponent', () => {
await form.fillForm(
{
'IP Address': '::',
'Discovery Authentication Method': 'CHAP',
'Discovery Authentication Group': 'Create New',
'Group ID': 1234,
User: 'userName',
Secret: '123456789qwerty',
'Secret (Confirm)': '123456789qwerty',
},
);

const saveButton = await loader.getHarness(MatButtonHarness.with({ text: 'Save' }));
await saveButton.click();
tick();

expect(spectator.inject(WebSocketService).call).toHaveBeenNthCalledWith(8, 'pool.dataset.create', [{
expect(spectator.inject(WebSocketService).call).toHaveBeenNthCalledWith(7, 'pool.dataset.create', [{
name: 'new_pool/test-name',
type: 'VOLUME',
volsize: 1073741824,
}]);

expect(spectator.inject(WebSocketService).call).toHaveBeenNthCalledWith(9, 'iscsi.extent.create', [{
expect(spectator.inject(WebSocketService).call).toHaveBeenNthCalledWith(8, 'iscsi.extent.create', [{
blocksize: 512,
disk: 'zvol/my+pool/test_zvol',
insecure_tpc: true,
Expand All @@ -146,25 +140,17 @@ describe('IscsiWizardComponent', () => {
xen: false,
}]);

expect(spectator.inject(WebSocketService).call).toHaveBeenNthCalledWith(10, 'iscsi.auth.create', [{
secret: '123456789qwerty',
tag: 1234,
user: 'userName',
}]);

expect(spectator.inject(WebSocketService).call).toHaveBeenNthCalledWith(11, 'iscsi.portal.create', [{
expect(spectator.inject(WebSocketService).call).toHaveBeenNthCalledWith(9, 'iscsi.portal.create', [{
comment: 'test-name',
discovery_authgroup: 12,
discovery_authmethod: 'CHAP',
listen: [{ ip: '::' }],
}]);

expect(spectator.inject(WebSocketService).call).toHaveBeenNthCalledWith(12, 'iscsi.initiator.create', [{
expect(spectator.inject(WebSocketService).call).toHaveBeenNthCalledWith(10, 'iscsi.initiator.create', [{
comment: 'test-name',
initiators: ['initiator1', 'initiator2'],
}]);

expect(spectator.inject(WebSocketService).call).toHaveBeenNthCalledWith(13, 'iscsi.target.create', [{
expect(spectator.inject(WebSocketService).call).toHaveBeenNthCalledWith(11, 'iscsi.target.create', [{
name: 'test-name',
groups: [{
auth: null,
Expand All @@ -174,7 +160,7 @@ describe('IscsiWizardComponent', () => {
}],
}]);

expect(spectator.inject(WebSocketService).call).toHaveBeenNthCalledWith(14, 'iscsi.targetextent.create', [{
expect(spectator.inject(WebSocketService).call).toHaveBeenNthCalledWith(12, 'iscsi.targetextent.create', [{
extent: 11,
target: 15,
}]);
Expand Down
Loading