Skip to content
This repository has been archived by the owner on Mar 25, 2023. It is now read-only.

Commit

Permalink
feat(security-group): assign and remove security groups in VM sidebar (
Browse files Browse the repository at this point in the history
…#595)

PR Close #595
  • Loading branch information
dron8552 committed Feb 4, 2019
2 parents 1af663e + 7f3bb8b commit 9f1975f
Show file tree
Hide file tree
Showing 22 changed files with 636 additions and 44 deletions.
41 changes: 41 additions & 0 deletions src/app/reducers/security-groups/redux/sg.reducers.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as securityGroupSelectors from './sg.reducers';
import { SecurityGroup } from '../../../security-group/sg.model';

describe('Security Group Selectors', () => {
it('should get sorted groups', () => {
const list = [
{
id: '1',
name: 'sg1',
},
{
id: '2',
name: 'sg2',
},
] as SecurityGroup[];

const preselected = [
{
id: '2',
name: 'sg2',
},
] as SecurityGroup[];

const sortedList = [
{
id: '2',
name: 'sg2',
isPreselected: true,
},
{
id: '1',
name: 'sg1',
isPreselected: false,
},
] as SecurityGroup[];

expect(
securityGroupSelectors.getSortedSecurityGroupForVmDetails.projector(list, preselected),
).toEqual(sortedList);
});
});
93 changes: 86 additions & 7 deletions src/app/reducers/security-groups/redux/sg.reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,23 @@ import * as securityGroupActions from './sg.actions';
import { Utils } from '../../../shared/services/utils/utils.service';
import { configSelectors } from '../../../root-store/config';
import { UserTagsSelectors } from '../../../root-store/server-data/user-tags';
import * as fromVMs from '../../vm/redux/vm.reducers';
import { VirtualMachine } from '../../../vm';

const sortBySelected = (a: SecurityGroup, b: SecurityGroup) => {
const aIsPreselected = a.isPreselected;
const bIsPreselected = b.isPreselected;

if ((aIsPreselected && bIsPreselected) || (!aIsPreselected && !bIsPreselected)) {
return a.name.localeCompare(b.name);
}
if (aIsPreselected && !bIsPreselected) {
return -1;
}
if (!aIsPreselected && bIsPreselected) {
return 1;
}
};

export interface State {
list: ListState;
Expand Down Expand Up @@ -160,15 +177,30 @@ export const { selectIds, selectEntities, selectAll, selectTotal } = adapter.get
getSecurityGroupsEntitiesState,
);

export const filters = createSelector(getSecurityGroupsEntitiesState, state => state.filters);
export const filters = createSelector(
getSecurityGroupsEntitiesState,
state => state.filters,
);

export const viewMode = createSelector(filters, state => state.viewMode);
export const viewMode = createSelector(
filters,
state => state.viewMode,
);

export const query = createSelector(filters, state => state.query);
export const query = createSelector(
filters,
state => state.query,
);

export const selectOrphanSG = createSelector(filters, state => state.selectOrphanSG);
export const selectOrphanSG = createSelector(
filters,
state => state.selectOrphanSG,
);

export const filterSelectedAccountIds = createSelector(filters, state => state.selectedAccountIds);
export const filterSelectedAccountIds = createSelector(
filters,
state => state.selectedAccountIds,
);

export const getSelectedId = createSelector(
getSecurityGroupsEntitiesState,
Expand All @@ -181,9 +213,15 @@ export const getSelectedSecurityGroup = createSelector(
(state, selectedId) => state.entities[selectedId],
);

export const isListLoading = createSelector(getSecurityGroupsEntitiesState, state => state.loading);
export const isListLoading = createSelector(
getSecurityGroupsEntitiesState,
state => state.loading,
);

export const isFormLoading = createSelector(getSecurityGroupsFormState, state => state.loading);
export const isFormLoading = createSelector(
getSecurityGroupsFormState,
state => state.loading,
);

const selectDefaultSecurityGroupName = createSelector(
configSelectors.get('defaultSecurityGroupName'),
Expand Down Expand Up @@ -275,6 +313,37 @@ export const selectSecurityGroupsForVmCreation = createSelector(
},
);

const sortSecurityGroups = (securityGroups: SecurityGroup[]) => {
return securityGroups.sort(sortBySelected);
};

export const setSecurityGroupState = (
securityGroups: SecurityGroup[],
preselectedGroups: SecurityGroup[],
): SecurityGroup[] => {
if (preselectedGroups) {
const list = securityGroups.map(group => {
const isPreselected = !!preselectedGroups.find(preselected => {
if (preselected && group) {
return preselected.id === group.id;
}
});
return { ...group, isPreselected };
});
return list;
}
return securityGroups;
};

export const getSortedSecurityGroupForVmDetails = createSelector(
selectSecurityGroupsForVmCreation,
fromVMs.getSelectedVmSecurityGroup,
(securityGroup: SecurityGroup[], preselectedGroups: SecurityGroup[]): SecurityGroup[] => {
const list = setSecurityGroupState(securityGroup, preselectedGroups);
return sortSecurityGroups(list);
},
);

export const selectDefaultSecurityGroup = createSelector(
selectAll,
selectDefaultSecurityGroupName,
Expand All @@ -287,3 +356,13 @@ export const selectDefaultSecurityGroup = createSelector(
return { ...defaultGroup, name: defaultSecurityGroupName };
},
);

export const getUsingSGVMs = createSelector(
fromVMs.selectAll,
getSelectedId,
(vms: VirtualMachine[], sGroupId: string) => {
const sGroupFilter = (vm: VirtualMachine) =>
vm.securitygroup.find(group => group.id === sGroupId);
return vms.filter(sGroupFilter);
},
);
14 changes: 14 additions & 0 deletions src/app/reducers/vm/redux/vm.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { VmState } from '../../../vm/shared/vm.model';
import { VmCreationState } from '../../../vm/vm-creation/data/vm-creation-state';
import { VmDeploymentMessage } from './vm-creation.effects';
import { FormState } from './vm.reducers';
import { SecurityGroup } from '../../../security-group/sg.model';

export const LOAD_VM_REQUEST = '[VM] LOAD_VM_REQUEST';
export const LOAD_VMS_REQUEST = '[VM] LOAD_VMS_REQUEST';
Expand All @@ -20,6 +21,7 @@ export const LOAD_SELECTED_VM = '[VM] LOAD_SELECTED_VM';
export const VM_CHANGE_DESCRIPTION = '[VM] VM_CHANGE_DESCRIPTION';
export const VM_CHANGE_SERVICE_OFFERING = '[VM] VM_CHANGE_SERVICE_OFFERING';
export const VM_CHANGE_AFFINITY_GROUP = '[VM] VM_CHANGE_AFFINITY_GROUP';
export const VM_CHANGE_SECURITY_GROUP = '[VM] VM_CHANGE_SECURITY_GROUP';
export const VM_CHANGE_INSTANCE_GROUP = '[VM] VM_CHANGE_INSTANCE_GROUP';
export const VM_REMOVE_INSTANCE_GROUP = '[VM] VM_REMOVE_INSTANCE_GROUP';
export const VM_ADD_SECONDARY_IP = '[VM] VM_ADD_SECONDARY_IP';
Expand Down Expand Up @@ -153,6 +155,17 @@ export class ChangeAffinityGroup implements Action {
) {}
}

export class ChangeSecurityGroup implements Action {
type = VM_CHANGE_SECURITY_GROUP;

constructor(
public payload: {
vm: VirtualMachine;
securityGroups: string[];
},
) {}
}

export class ChangeInstanceGroup implements Action {
type = VM_CHANGE_INSTANCE_GROUP;

Expand Down Expand Up @@ -489,6 +502,7 @@ export type Actions =
| LoadSelectedVM
| ChangeDescription
| ChangeServiceOffering
| ChangeSecurityGroup
| ChangeAffinityGroup
| ChangeInstanceGroup
| RemoveInstanceGroup
Expand Down
56 changes: 56 additions & 0 deletions src/app/reducers/vm/redux/vm.effects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1248,4 +1248,60 @@ describe('Virtual machine Effects', () => {

expect(effects.vmCreateSuccessLoadVolumes$).toBeObservable(expected);
});

it('should change affinity group for stopped vm', () => {
const spyChangeSG = spyOn(service, 'updateSecurityGroup').and.returnValue(of(list[1]));

const action = new vmActions.ChangeSecurityGroup({
securityGroups: ['sg1_id'],
vm: list[1],
});
const completion = new vmActions.UpdateVM(list[1]);

actions$.stream = hot('-a', { a: action });
const expected = cold('-b', { b: completion });

expect(effects.changeSecurityGroup$).toBeObservable(expected);
expect(spyChangeSG).toHaveBeenCalled();
expect(jobsNotificationService.add).toHaveBeenCalled();
});

it('should change affinity group for running vm', () => {
const spyChangeSG = spyOn(service, 'updateSecurityGroup').and.returnValue(of(list[0]));
const spyCommand = spyOn(service, 'command').and.returnValue(of(list[0]));
spyOn(dialogService, 'confirm').and.returnValue(of(true));

const action = new vmActions.ChangeSecurityGroup({
securityGroups: ['sg1_id'],
vm: list[0],
});
const completion = new vmActions.UpdateVM(list[0]);

actions$.stream = hot('-a', { a: action });
const expected = cold('-b', { b: completion });

expect(effects.changeSecurityGroup$).toBeObservable(expected);
expect(spyChangeSG).toHaveBeenCalled();
expect(spyCommand).toHaveBeenCalled();
expect(jobsNotificationService.add).toHaveBeenCalled();
});

it('should not change affinity group for running vm', () => {
const spyChangeSG = spyOn(service, 'updateSecurityGroup');
const spyCommand = spyOn(service, 'command');
const spyDialog = spyOn(dialogService, 'confirm').and.returnValue(of(false));

const action = new vmActions.ChangeSecurityGroup({
securityGroups: ['sg1_id'],
vm: list[0],
});

actions$.stream = hot('-a', { a: action });
const expected = cold('', []);

expect(effects.changeSecurityGroup$).toBeObservable(expected);
expect(spyDialog).toHaveBeenCalled();
expect(spyChangeSG).not.toHaveBeenCalled();
expect(spyCommand).not.toHaveBeenCalled();
});
});
49 changes: 49 additions & 0 deletions src/app/reducers/vm/redux/vm.effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,55 @@ export class VirtualMachinesEffects {
),
);

@Effect()
changeSecurityGroup$: Observable<Action> = this.actions$.pipe(
ofType(vmActions.VM_CHANGE_SECURITY_GROUP),
mergeMap((action: vmActions.ChangeSecurityGroup) => {
return this.askToStopVM(
action.payload.vm,
'VM_PAGE.VM_DETAILS.SECURITY_GROUP.STOP_MACHINE_FOR_SG',
).pipe(
switchMap(() => {
if (action.payload.vm.state === VmState.Running) {
return this.stop(action.payload.vm).pipe(map(() => action));
}
return of(action);
}),
switchMap(() => {
const notificationId = this.jobsNotificationService.add(
'NOTIFICATIONS.VM.CHANGE_SECURITY_GROUP_IN_PROGRESS',
);
const vm = action.payload.vm;
const securityGroupIds = action.payload.securityGroups.join();

return this.vmService.updateSecurityGroup(vm, securityGroupIds).pipe(
tap(() => {
const message = 'NOTIFICATIONS.VM.CHANGE_SECURITY_GROUP_DONE';
this.showNotificationsOnFinish(message, notificationId);
}),
switchMap(newVm => {
if (vm.state === VmState.Running) {
return this.start(newVm);
}
return of(new vmActions.UpdateVM(newVm));
}),
catchError((error: Error) => {
const message = 'NOTIFICATIONS.VM.CHANGE_SECURITY_GROUP_FAILED';
this.dialogService.showNotificationsOnFail(error, message, notificationId);
return of(
new vmActions.VMUpdateError({
error,
vm,
state: VmState.Stopped,
}),
);
}),
);
}),
);
}),
);

constructor(
private store: Store<State>,
private actions$: Actions,
Expand Down
17 changes: 6 additions & 11 deletions src/app/reducers/vm/redux/vm.reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import { VmCreationState } from '../../../vm/vm-creation/data/vm-creation-state'
import { VmCreationSecurityGroupData } from '../../../vm/vm-creation/security-group/vm-creation-security-group-data';
import { notSelectedSshKey } from '../../../vm/vm-creation/ssh-key-selector/ssh-key-selector.component';
import { noGroup } from '../../../vm/vm-filter/vm-filter.component';

import * as fromAccounts from '../../accounts/redux/accounts.reducers';
import * as affinityGroupActions from '../../affinity-groups/redux/affinity-groups.actions';
import * as fromSGroup from '../../security-groups/redux/sg.reducers';
import * as fromZones from '../../zones/redux/zones.reducers';
import * as vmActions from './vm.actions';

Expand Down Expand Up @@ -231,6 +231,11 @@ export const getSelectedVmAffinityGroups = createSelector(
vm => vm && vm.affinitygroup,
);

export const getSelectedVmSecurityGroup = createSelector(
getSelectedVM,
vm => vm && vm.securitygroup,
);

export const filters = createSelector(
getVMsEntitiesState,
state => state.filters,
Expand Down Expand Up @@ -279,16 +284,6 @@ export const selectVmGroups = createSelector(
},
);

export const getUsingSGVMs = createSelector(
selectAll,
fromSGroup.getSelectedId,
(vms: VirtualMachine[], sGroupId: string) => {
const sGroupFilter = (vm: VirtualMachine) =>
vm.securitygroup.find(group => group.id === sGroupId);
return vms.filter(sGroupFilter);
},
);

export const getAttachmentVMs = createSelector(
selectAll,
attachmentFilters,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ import * as fromVM from '../../reducers/vm/redux/vm.reducers';
@Component({
selector: 'cs-sg-details-container',
template: `
<cs-security-group-details
[securityGroup]="securityGroup$ | async"
></cs-security-group-details>
<cs-security-group-details [securityGroup]="securityGroup$ | async"></cs-security-group-details>
<cs-security-group-vm-list
[viewMode]="viewMode$ | async"
[vmList]="vmList$ | async"
Expand All @@ -19,7 +17,7 @@ import * as fromVM from '../../reducers/vm/redux/vm.reducers';
})
export class SecurityGroupDetailsContainerComponent {
readonly securityGroup$ = this.store.pipe(select(fromSecurityGroups.getSelectedSecurityGroup));
readonly vmList$ = this.store.pipe(select(fromVM.getUsingSGVMs));
readonly vmList$ = this.store.pipe(select(fromSecurityGroups.getUsingSGVMs));
readonly viewMode$ = this.store.pipe(select(fromSecurityGroups.viewMode));

constructor(private store: Store<State>) {}
Expand Down
Loading

0 comments on commit 9f1975f

Please sign in to comment.