diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index e09b69dad4..d8f0a2ca89 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -57,7 +57,7 @@ const routes: Routes = [
path: '**',
redirectTo: 'instances'
}
- ]
+ ],
},
{
path: '**',
diff --git a/src/app/core/services/system-tags.service.ts b/src/app/core/services/system-tags.service.ts
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts
index 4f3ad53c3f..f8e1ed5932 100644
--- a/src/app/home/home.component.ts
+++ b/src/app/home/home.component.ts
@@ -30,7 +30,7 @@ export class HomeComponent extends WithUnsubscribe() implements OnInit {
this.auth.loggedIn.pipe(
takeUntil(this.unsubscribe$),
- filter(isLoggedIn => !!isLoggedIn))
+ filter(isLoggedIn => isLoggedIn))
.subscribe(() => {
this.store.dispatch(new authActions.LoadUserAccountRequest({
name: this.auth.user.account,
diff --git a/src/app/reducers/vm/redux/vm-creation.effects.ts b/src/app/reducers/vm/redux/vm-creation.effects.ts
index 34f726be10..d6c13bc4c4 100644
--- a/src/app/reducers/vm/redux/vm-creation.effects.ts
+++ b/src/app/reducers/vm/redux/vm-creation.effects.ts
@@ -44,6 +44,7 @@ import * as fromTemplates from '../../templates/redux/template.reducers';
import * as fromVMs from './vm.reducers';
import * as fromVMModule from '../../../vm/selectors';
import { KeyboardLayout } from '../../../shared/types';
+import { ComputeOfferingViewModel } from '../../../vm/view-models';
interface VmCreationParams {
affinityGroupNames?: string;
@@ -160,11 +161,14 @@ export class VirtualMachineCreationEffects {
this.store.pipe(select(fromDiskOfferings.selectAll)),
this.store.pipe(select(configSelectors.get('defaultComputeOffering')))
),
- map((
- [action, vmCreationState, zones, templates, serviceOfferings, diskOfferings, defaultComputeOfferings]: [
- vmActions.VmFormUpdate, VmCreationState, Zone[], BaseTemplateModel[], ServiceOffering[], DiskOffering[],
- DefaultComputeOffering[]
- ]) => {
+ map(([action, vmCreationState, zones, templates, serviceOfferings, diskOfferings, defaultComputeOfferings]: [
+ vmActions.VmFormUpdate,
+ VmCreationState, Zone[],
+ BaseTemplateModel[],
+ ComputeOfferingViewModel[],
+ DiskOffering[],
+ DefaultComputeOffering[]
+ ]) => {
if (action.payload.zone) {
let updates = {};
@@ -670,10 +674,10 @@ export class VirtualMachineCreationEffects {
}
private getPreselectedOffering(
- offerings: ServiceOffering[],
+ offerings: ComputeOfferingViewModel[],
zone: Zone,
defaultComputeOfferingConfiguration: DefaultComputeOffering[]
- ): ServiceOffering {
+ ): ComputeOfferingViewModel {
const firstOffering = offerings[0];
const configForCurrentZone = defaultComputeOfferingConfiguration.find(config => config.zoneId === zone.id);
if (!configForCurrentZone) {
diff --git a/src/app/service-offering/custom-service-offering/custom-service-offering.component.html b/src/app/service-offering/custom-service-offering/custom-service-offering.component.html
index a994ada989..5c11cfee5d 100644
--- a/src/app/service-offering/custom-service-offering/custom-service-offering.component.html
+++ b/src/app/service-offering/custom-service-offering/custom-service-offering.component.html
@@ -47,6 +47,10 @@
{{ 'SERVICE_OFFERING.CUSTOM_SERVICE_OFFERING.MEMORY' | translate }}
+
+ {{ 'ERRORS.COMPUTE_OFFERING.RESOURCE_LIMIT_EXCEEDED' | translate }}
+
+
diff --git a/src/app/service-offering/custom-service-offering/custom-service-offering.component.scss b/src/app/service-offering/custom-service-offering/custom-service-offering.component.scss
index a91094ec88..ae72dcc5d0 100644
--- a/src/app/service-offering/custom-service-offering/custom-service-offering.component.scss
+++ b/src/app/service-offering/custom-service-offering/custom-service-offering.component.scss
@@ -9,3 +9,8 @@ h5 {
margin-top: 0;
}
}
+
+.error-message {
+ color: #f44336;
+ font-size: 13px;
+}
diff --git a/src/app/service-offering/custom-service-offering/custom-service-offering.component.ts b/src/app/service-offering/custom-service-offering/custom-service-offering.component.ts
index 1a1efe1e9a..8e652e3fb4 100644
--- a/src/app/service-offering/custom-service-offering/custom-service-offering.component.ts
+++ b/src/app/service-offering/custom-service-offering/custom-service-offering.component.ts
@@ -1,25 +1,29 @@
-import { Component, Inject } from '@angular/core';
+import { Component, Inject, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms'
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
import { ComputeOfferingViewModel } from '../../vm/view-models';
+import { Account } from '../../shared/models';
@Component({
selector: 'cs-custom-service-offering',
templateUrl: 'custom-service-offering.component.html',
styleUrls: ['custom-service-offering.component.scss']
})
-export class CustomServiceOfferingComponent {
+export class CustomServiceOfferingComponent implements OnInit {
public offering: ComputeOfferingViewModel;
public hardwareForm: FormGroup;
+ public account: Account;
constructor(
@Inject(MAT_DIALOG_DATA) data,
- public dialogRef: MatDialogRef,
+ public dialogRef: MatDialogRef
) {
- const { offering } = data;
- this.offering = offering;
+ this.offering = data.offering;
+ this.account = data.account;
+ }
+ public ngOnInit() {
this.createForm();
}
@@ -37,9 +41,12 @@ export class CustomServiceOfferingComponent {
private createForm() {
// input text=number provide all other validation for current restrictions
this.hardwareForm = new FormGroup({
- cpuNumber: new FormControl(this.offering.cpunumber, Validators.required),
- cpuSpeed: new FormControl(this.offering.cpuspeed, Validators.required),
- memory: new FormControl(this.offering.memory, Validators.required),
+ cpuNumber: new FormControl(
+ { value: this.offering.cpunumber, disabled: !this.offering.isAvailableByResources }, Validators.required),
+ cpuSpeed: new FormControl(
+ { value: this.offering.cpuspeed, disabled: !this.offering.isAvailableByResources }, Validators.required),
+ memory: new FormControl(
+ { value: this.offering.memory, disabled: !this.offering.isAvailableByResources }, Validators.required),
});
}
}
diff --git a/src/app/service-offering/service-offering-dialog/service-offering-dialog.component.html b/src/app/service-offering/service-offering-dialog/service-offering-dialog.component.html
index 622219ed11..6555a2ff02 100644
--- a/src/app/service-offering/service-offering-dialog/service-offering-dialog.component.html
+++ b/src/app/service-offering/service-offering-dialog/service-offering-dialog.component.html
@@ -14,6 +14,7 @@
[classes]="classes"
[selectedClasses]="selectedClasses"
[query]="query"
+ [account]="account"
[offeringList]="serviceOfferings"
[selectedOffering]="serviceOffering"
[showFields]="showFields"
@@ -25,7 +26,12 @@
*ngIf="showRebootMessage"
>{{ "SERVICE_OFFERING.VM_WILL_BE_RESTARTED" | translate }}
+
+ {{ 'ERRORS.COMPUTE_OFFERING.RESOURCE_LIMIT_EXCEEDED' | translate }}
+
+
+
-
diff --git a/src/app/service-offering/service-offering-list/service-offering-list.component.ts b/src/app/service-offering/service-offering-list/service-offering-list.component.ts
index bbcda815a9..18eacf1d3d 100644
--- a/src/app/service-offering/service-offering-list/service-offering-list.component.ts
+++ b/src/app/service-offering/service-offering-list/service-offering-list.component.ts
@@ -5,7 +5,7 @@ import { Observable } from 'rxjs';
import { filter } from 'rxjs/operators';
import { classesFilter } from '../../reducers/service-offerings/redux/service-offerings.reducers';
-import { ComputeOfferingClass, ServiceOffering } from '../../shared/models';
+import { Account, ComputeOfferingClass, ServiceOffering } from '../../shared/models';
import { CustomServiceOfferingComponent } from '../custom-service-offering/custom-service-offering.component';
import { Language } from '../../shared/types';
import { ComputeOfferingViewModel } from '../../vm/view-models';
@@ -20,9 +20,10 @@ export class ServiceOfferingListComponent implements OnChanges {
@Input() public classes: Array;
@Input() public selectedClasses: Array;
@Input() public query: string;
- @Input() public selectedOffering: ServiceOffering;
+ @Input() public selectedOffering: ComputeOfferingViewModel;
@Input() public isLoading = false;
@Input() public showFields: boolean;
+ @Input() public account: Account;
@Output() public selectedOfferingChange = new EventEmitter();
public list: Array<{ soClass: ComputeOfferingClass, items: MatTableDataSource }>;
@@ -61,7 +62,8 @@ export class ServiceOfferingListComponent implements OnChanges {
return this.dialog.open(CustomServiceOfferingComponent, {
width: '370px',
data: {
- offering
+ offering,
+ account: this.account
}
}).afterClosed();
@@ -100,7 +102,7 @@ export class ServiceOfferingListComponent implements OnChanges {
}
}
- public filterOfferings(list: ServiceOffering[], soClass: ComputeOfferingClass) {
+ public filterOfferings(list: ComputeOfferingViewModel[], soClass: ComputeOfferingClass) {
const classesMap = [soClass].reduce((m, i) => ({ ...m, [i.id]: i }), {});
return list.filter(offering => classesFilter(offering, this.classes, classesMap));
}
diff --git a/src/app/shared/actions/volume-actions/volume-resize.container.ts b/src/app/shared/actions/volume-actions/volume-resize.container.ts
index 4b54524831..b82616e1af 100644
--- a/src/app/shared/actions/volume-actions/volume-resize.container.ts
+++ b/src/app/shared/actions/volume-actions/volume-resize.container.ts
@@ -32,7 +32,7 @@ export class VolumeResizeContainerComponent implements OnInit {
public volume: Volume;
- public maxSize = 2;
+ public maxSize = '2';
constructor(
public authService: AuthService,
@@ -51,7 +51,7 @@ export class VolumeResizeContainerComponent implements OnInit {
take(1),
filter(account => !!account))
.subscribe((account: Account) => {
- this.maxSize = Number(account.primarystorageavailable);
+ this.maxSize = account.primarystorageavailable;
});
}
diff --git a/src/app/shared/models/account-user.model.ts b/src/app/shared/models/account-user.model.ts
index 7e1ba82377..7e109b3215 100644
--- a/src/app/shared/models/account-user.model.ts
+++ b/src/app/shared/models/account-user.model.ts
@@ -8,22 +8,22 @@ export interface AccountUser extends BaseModelInterface {
firstname: string;
lastname: string;
email: string;
- password?: string;
created: string;
state: string;
account: string;
accounttype: number;
- roleid: string;
roletype: AccountType;
rolename: AccountType;
+ roleid: string;
domain: string;
domainid: string;
timezone: string;
accountid: string;
iscallerchilddomain: boolean;
isdefault: boolean;
- secretkey: string;
- apikey: string;
+ password?: string;
+ apikey?: string;
+ secretkey?: string;
}
export interface ApiKeys {
diff --git a/src/app/shared/models/account.model.ts b/src/app/shared/models/account.model.ts
index d839a12943..7a0aff6900 100644
--- a/src/app/shared/models/account.model.ts
+++ b/src/app/shared/models/account.model.ts
@@ -41,55 +41,55 @@ export class AccountData {
export interface Account extends BaseModelInterface {
accounttype: AccountType;
- cpuavailable: number;
- cpulimit: number;
+ cpuavailable: string;
+ cpulimit: string;
cputotal: number;
domain: string;
- fullDomain: string;
domainid: string;
id: string;
- ipavailable: number;
- iplimit: number;
+ ipavailable: string;
+ iplimit: string;
iptotal: number;
isdefault: false;
- memoryavailable: number;
- memorylimit: number;
+ memoryavailable: string;
+ memorylimit: string;
memorytotal: number;
name: string;
- networkavailable: number;
- networklimit: number;
+ networkavailable: string;
+ networklimit: string;
networktotal: number;
primarystorageavailable: string;
- primarystoragelimit: number;
+ primarystoragelimit: string;
primarystoragetotal: number;
- role: string;
roleid: string;
rolename: string;
roletype: string;
- receivedbytes: number;
- sentbytes: number;
- secondarystorageavailable: number;
- secondarystoragelimit: number;
+ receivedbytes?: number;
+ sentbytes?: number;
+ secondarystorageavailable: string;
+ secondarystoragelimit: string;
secondarystoragetotal: number;
- snapshotavailable: number;
- snapshotlimit: number;
+ snapshotavailable: string;
+ snapshotlimit: string;
snapshottotal: number;
state: string;
- templateavailable: number;
- templatelimit: number;
+ templateavailable: string;
+ templatelimit: string;
templatetotal: number;
user: Array;
- vmavailable: number;
- vmlimit: number;
+ vmavailable: string;
+ vmlimit: string;
vmrunning: number;
vmstopped: number;
vmtotal: number;
- volumeavailable: number;
- volumelimit: number;
+ volumeavailable: string;
+ volumelimit: string;
volumetotal: number;
- vpcavailable: number;
- vpclimit: number;
+ vpcavailable: string;
+ vpclimit: string;
vpctotal: number;
+ role?: string;
+ fullDomain?: string;
}
export const isAdmin = (account: Account) => account.accounttype !== AccountType.User;
diff --git a/src/app/shared/models/config/index.ts b/src/app/shared/models/config/index.ts
index c5a2d3909a..a816dbc71d 100644
--- a/src/app/shared/models/config/index.ts
+++ b/src/app/shared/models/config/index.ts
@@ -9,3 +9,4 @@ export * from './image-group.model';
export * from './service-offering-availability.interface';
export * from './offering-compatibility-policy.interface';
export * from './sidenav-config-element.interface';
+export * from './custom-compute-offering-parameters.interface';
diff --git a/src/app/shared/models/offering.model.ts b/src/app/shared/models/offering.model.ts
index bf29039ca7..e45f020385 100644
--- a/src/app/shared/models/offering.model.ts
+++ b/src/app/shared/models/offering.model.ts
@@ -9,15 +9,15 @@ export interface Offering extends BaseModelInterface {
id: string;
name: string;
displaytext: string;
- diskBytesReadRate: number;
- diskBytesWriteRate: number;
- diskIopsReadRate: number;
- diskIopsWriteRate: number;
iscustomized: boolean;
- miniops: number;
- maxiops: number;
storagetype: string;
provisioningtype: string;
+ diskBytesReadRate?: number;
+ diskBytesWriteRate?: number;
+ diskIopsReadRate?: number;
+ diskIopsWriteRate?: number;
+ miniops?: number;
+ maxiops?: number;
}
export const isOfferingLocal = (offering: Offering) => offering.storagetype === StorageTypes.local;
diff --git a/src/app/shared/models/service-offering.model.ts b/src/app/shared/models/service-offering.model.ts
index 07229c19f1..99c6b3687b 100644
--- a/src/app/shared/models/service-offering.model.ts
+++ b/src/app/shared/models/service-offering.model.ts
@@ -4,19 +4,19 @@ import { userTagKeys } from '../../tags/tag-keys';
export interface ServiceOffering extends Offering {
created: string;
- cpunumber: number;
- cpuspeed: number;
- memory: number;
- networkrate: string;
offerha: boolean;
limitcpuuse: boolean;
isvolatile: boolean;
issystem: boolean;
defaultuse: boolean;
- deploymentplanner: string;
- domain: string;
- hosttags: string;
- tags: Array;
+ cpunumber?: number;
+ cpuspeed?: number;
+ memory?: number;
+ tags?: Array;
+ domain?: string;
+ hosttags?: string;
+ deploymentplanner?: string;
+ networkrate?: string;
}
export const ServiceOfferingType = {
diff --git a/src/app/template/template-tags/tags.component.html b/src/app/template/template-tags/tags.component.html
deleted file mode 100644
index 363508e192..0000000000
--- a/src/app/template/template-tags/tags.component.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/src/app/template/template-tags/tags.component.ts b/src/app/template/template-tags/tags.component.ts
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/src/app/template/template-tags/template-tags.component.html b/src/app/template/template-tags/template-tags.component.html
index 1baf64c5bb..363508e192 100644
--- a/src/app/template/template-tags/template-tags.component.html
+++ b/src/app/template/template-tags/template-tags.component.html
@@ -1,8 +1,9 @@
-
+
+
+
diff --git a/src/app/template/template-tags/template-tags.component.ts b/src/app/template/template-tags/template-tags.component.ts
index 38b1cfd21c..11a752e420 100644
--- a/src/app/template/template-tags/template-tags.component.ts
+++ b/src/app/template/template-tags/template-tags.component.ts
@@ -10,7 +10,7 @@ import { KeyValuePair } from '../../tags/tags-view/tags-view.component';
@Component({
selector: 'cs-template-tags',
- templateUrl: 'tags.component.html'
+ templateUrl: 'template-tags.component.html'
})
export class TemplateTagsComponent extends TagsComponent {
@Input() public entity: BaseTemplateModel;
diff --git a/src/app/vm/selectors/service-offering.selectors.ts b/src/app/vm/selectors/service-offering.selectors.ts
index 81648d6a76..98f36864e2 100644
--- a/src/app/vm/selectors/service-offering.selectors.ts
+++ b/src/app/vm/selectors/service-offering.selectors.ts
@@ -1,7 +1,6 @@
import { createSelector } from '@ngrx/store';
import { VmCompatibilityPolicy } from '../shared/vm-compatibility-policy';
-import { ResourceStats } from '../../shared/services/resource-usage.service';
import { ComputeOfferingViewModel } from '../view-models';
import { isOfferingLocal } from '../../shared/models/offering.model';
import {
@@ -10,7 +9,6 @@ import {
ServiceOfferingAvailability
} from '../../shared/models/config';
import { ServiceOffering, ServiceOfferingType, Zone } from '../../shared/models';
-import { getComputeOfferingViewModel } from './view-models';
import { configSelectors } from '../../root-store';
import * as fromZones from '../../reducers/zones/redux/zones.reducers';
import * as fromAuths from '../../reducers/auth/redux/auth.reducers';
@@ -21,6 +19,10 @@ import {
filterSelectedViewMode,
getSelectedOffering,
} from '../../reducers/service-offerings/redux/service-offerings.reducers';
+import {
+ getComputeOfferingForVmCreation,
+ getComputeOfferingForVmEditing
+} from './view-models/compute-offering-view-model.selector';
const isComputeOfferingAvailableInZone = (
offering: ServiceOffering,
@@ -50,32 +52,8 @@ const getOfferingsAvailableInZone = (
});
};
-const getAvailableByResourcesSync = (
- serviceOfferings: ComputeOfferingViewModel[],
- availability: ServiceOfferingAvailability,
- resourceUsage: ResourceStats,
- zone: Zone
-) => {
- const availableInZone = getOfferingsAvailableInZone(serviceOfferings, availability, zone);
-
- return availableInZone.filter(offering => {
- let enoughCpus;
- let enoughMemory;
-
- if (offering.iscustomized) {
- enoughCpus = resourceUsage.available.cpus >= offering.customOfferingRestrictions.cpunumber.min;
- enoughMemory = resourceUsage.available.memory >= offering.customOfferingRestrictions.memory.min;
- } else {
- enoughCpus = resourceUsage.available.cpus >= offering.cpunumber;
- enoughMemory = resourceUsage.available.memory >= offering.memory;
- }
-
- return enoughCpus && enoughMemory;
- });
-};
-
export const getAvailableOfferingsForVmCreation = createSelector(
- getComputeOfferingViewModel,
+ getComputeOfferingForVmCreation,
configSelectors.get('serviceOfferingAvailability'),
fromVMs.getVMCreationZone,
fromAuths.getUserAccount,
@@ -84,13 +62,12 @@ export const getAvailableOfferingsForVmCreation = createSelector(
return [];
}
- const resourceUsage = ResourceStats.fromAccount([user]);
- return getAvailableByResourcesSync(serviceOfferings, availability, resourceUsage, zone);
+ return getOfferingsAvailableInZone(serviceOfferings, availability, zone);
}
);
export const getAvailableOfferings = createSelector(
- getComputeOfferingViewModel,
+ getComputeOfferingForVmEditing,
getSelectedOffering,
configSelectors.get('serviceOfferingAvailability'),
configSelectors.get('offeringCompatibilityPolicy'),
@@ -108,8 +85,7 @@ export const getAvailableOfferings = createSelector(
return [];
}
- const resourceUsage = ResourceStats.fromAccount([user]);
- const availableOfferings = getAvailableByResourcesSync(serviceOfferings, availability, resourceUsage, zone);
+ const availableOfferings = getOfferingsAvailableInZone(serviceOfferings, availability, zone);
const filterByCompatibilityPolicy = VmCompatibilityPolicy.getFilter(compatibilityPolicy, currentOffering);
diff --git a/src/app/vm/selectors/view-models/compute-offering-view-model.selector.spec.ts b/src/app/vm/selectors/view-models/compute-offering-view-model.selector.spec.ts
new file mode 100644
index 0000000000..433df25fc9
--- /dev/null
+++ b/src/app/vm/selectors/view-models/compute-offering-view-model.selector.spec.ts
@@ -0,0 +1,212 @@
+import { account, customComputeOffering, fixedComputeOffering, vm } from '../../../../testutils/data';
+import { nonCustomizableProperties } from '../../../core/config/default-configuration';
+import { ComputeOfferingViewModel } from '../../view-models';
+import { Account } from '../../../shared/models';
+import { CustomComputeOfferingParameters } from '../../../shared/models/config/index';
+import {
+ getComputeOfferingForVmCreation,
+ getComputeOfferingForVmEditing
+} from './compute-offering-view-model.selector';
+
+describe('GetComputeOfferingForVmCreationSelector', () => {
+ it('isAvailableByResources should be true in fixed compute offering params which satisfy memory and cpu resources',
+ () => {
+ const [computeOfferingViewModel]: ComputeOfferingViewModel[] = getComputeOfferingForVmCreation.projector(
+ account,
+ [fixedComputeOffering],
+ [],
+ nonCustomizableProperties.defaultCustomComputeOfferingRestrictions,
+ nonCustomizableProperties.customComputeOfferingHardwareValues,
+ []
+ );
+ expect(computeOfferingViewModel.isAvailableByResources).toEqual(true);
+ });
+
+ it('should be false in fixed compute offering params which unsatisfied memory resources', () => {
+ const limitedAccount: Account = { ...account, memoryavailable: String(fixedComputeOffering.memory - 10) };
+ const [computeOfferingViewModel]: ComputeOfferingViewModel[] = getComputeOfferingForVmCreation.projector(
+ limitedAccount,
+ [fixedComputeOffering],
+ [],
+ nonCustomizableProperties.defaultCustomComputeOfferingRestrictions,
+ nonCustomizableProperties.customComputeOfferingHardwareValues,
+ []
+ );
+ expect(computeOfferingViewModel.isAvailableByResources).toEqual(false);
+ });
+
+ it('should be false in fixed compute offering params which unsatisfied cpu resources', () => {
+ const limitedAccount: Account = { ...account, cpuavailable: String(fixedComputeOffering.cpunumber - 1) };
+ const [computeOfferingViewModel]: ComputeOfferingViewModel[] = getComputeOfferingForVmCreation.projector(
+ limitedAccount,
+ [fixedComputeOffering],
+ [],
+ nonCustomizableProperties.defaultCustomComputeOfferingRestrictions,
+ nonCustomizableProperties.customComputeOfferingHardwareValues,
+ []
+ );
+ expect(computeOfferingViewModel.isAvailableByResources).toEqual(false);
+ });
+
+ it('should be true in custom compute offering params which satisfy memory and cpu resources', () => {
+ const [computeOfferingViewModel]: ComputeOfferingViewModel[] = getComputeOfferingForVmCreation.projector(
+ account,
+ [customComputeOffering],
+ [],
+ nonCustomizableProperties.defaultCustomComputeOfferingRestrictions,
+ nonCustomizableProperties.customComputeOfferingHardwareValues,
+ []
+ );
+ expect(computeOfferingViewModel.isAvailableByResources).toEqual(true);
+ });
+
+ it('should be false in custom compute offering params which unsatisfied memory resources', () => {
+ const memoryavailable = String(nonCustomizableProperties.customComputeOfferingHardwareValues.memory - 10);
+ const limitedAccount: Account = { ...account, memoryavailable };
+ const [computeOfferingViewModel]: ComputeOfferingViewModel[] = getComputeOfferingForVmCreation.projector(
+ limitedAccount,
+ [customComputeOffering],
+ [],
+ nonCustomizableProperties.defaultCustomComputeOfferingRestrictions,
+ nonCustomizableProperties.customComputeOfferingHardwareValues,
+ []
+ );
+ expect(computeOfferingViewModel.isAvailableByResources).toEqual(false);
+ });
+
+ it('should be false in custom compute offering params which unsatisfied cpu resources', () => {
+ const cpuavailable = String(nonCustomizableProperties.customComputeOfferingHardwareValues.cpunumber - 1);
+ const limitedAccount: Account = { ...account, cpuavailable };
+ const [computeOfferingViewModel]: ComputeOfferingViewModel[] = getComputeOfferingForVmCreation.projector(
+ limitedAccount,
+ [customComputeOffering],
+ [],
+ nonCustomizableProperties.defaultCustomComputeOfferingRestrictions,
+ nonCustomizableProperties.customComputeOfferingHardwareValues,
+ []
+ );
+ expect(computeOfferingViewModel.isAvailableByResources).toEqual(false);
+ });
+
+ it('must set values within restrictions and resources for custom compute offering', () => {
+ /**
+ * Min Value Max Resource
+ * cpu 2 7 8 5 => Value = MaxRestrictions = 5
+ * memory 512 4000 8192 2000 => Value = MaxRestrictions = 2000
+ */
+ const cpuavailable = '5';
+ const memoryavailable = '2000';
+ const limitedAccount: Account = { ...account, memoryavailable, cpuavailable };
+
+ const customComputeOfferingParameters: CustomComputeOfferingParameters[] = [
+ {
+ offeringId: customComputeOffering.id,
+ cpunumber: { min: 2, max: 8, value: 7 },
+ cpuspeed: { min: 1000, max: 3000, value: 1500 },
+ memory: { min: 512, max: 8192, value: 4000 }
+ }
+ ];
+
+ const [computeOfferingViewModel]: ComputeOfferingViewModel[] = getComputeOfferingForVmCreation.projector(
+ limitedAccount,
+ [customComputeOffering],
+ customComputeOfferingParameters,
+ nonCustomizableProperties.defaultCustomComputeOfferingRestrictions,
+ nonCustomizableProperties.customComputeOfferingHardwareValues,
+ []
+ );
+ expect(computeOfferingViewModel.cpunumber).toBe(5);
+ expect(computeOfferingViewModel.memory).toBe(2000);
+ expect(computeOfferingViewModel.customOfferingRestrictions.cpunumber.max).toBe(5);
+ expect(computeOfferingViewModel.customOfferingRestrictions.memory.max).toBe(2000);
+ });
+
+ it('must set default values within restrictions and resources for custom compute offering', () => {
+ /**
+ * Min Value Max Resource
+ * cpu 2 7 8 5 => Value = MaxRestrictions = 5
+ * memory 512 4000 4000 8000 => Value = MaxRestrictions = 4000
+ */
+ const cpuavailable = '5';
+ const memoryavailable = '8000';
+ const limitedAccount: Account = { ...account, memoryavailable, cpuavailable };
+
+ const customComputeOfferingParameters: CustomComputeOfferingParameters[] = [
+ {
+ offeringId: customComputeOffering.id,
+ cpunumber: { min: 2, max: 8, value: 7 },
+ cpuspeed: { min: 1000, max: 3000, value: 1500 },
+ memory: { min: 512, max: 4000, value: 4000 }
+ }
+ ];
+
+ const [computeOfferingViewModel]: ComputeOfferingViewModel[] = getComputeOfferingForVmCreation.projector(
+ limitedAccount,
+ [customComputeOffering],
+ customComputeOfferingParameters,
+ nonCustomizableProperties.defaultCustomComputeOfferingRestrictions,
+ nonCustomizableProperties.customComputeOfferingHardwareValues,
+ []
+ );
+ expect(computeOfferingViewModel.cpunumber).toBe(5);
+ expect(computeOfferingViewModel.memory).toBe(4000);
+ expect(computeOfferingViewModel.customOfferingRestrictions.cpunumber.max).toBe(5);
+ expect(computeOfferingViewModel.customOfferingRestrictions.memory.max).toBe(4000);
+ });
+});
+
+describe('GetComputeOfferingForVmEditingSelector', () => {
+ it('isAvailableByResources should be true in fixed compute offering params which satisfy memory and cpu resources',
+ () => {
+ const [computeOfferingViewModel]: ComputeOfferingViewModel[] = getComputeOfferingForVmEditing.projector(
+ account,
+ [fixedComputeOffering],
+ [],
+ nonCustomizableProperties.defaultCustomComputeOfferingRestrictions,
+ nonCustomizableProperties.customComputeOfferingHardwareValues,
+ [],
+ vm
+ );
+ expect(computeOfferingViewModel.isAvailableByResources).toEqual(true);
+ });
+
+ it('isAvailableByResources should be true, cause satisfy resources plus used resources in editing vm',
+ () => {
+ const cpuavailable = '0';
+ const memoryavailable = '512';
+ const limitedAccount: Account = { ...account, memoryavailable, cpuavailable };
+ const updatedVm = { ...vm, memory: '512', cpuNumber: 1 };
+
+ const [computeOfferingViewModel]: ComputeOfferingViewModel[] = getComputeOfferingForVmEditing.projector(
+ limitedAccount,
+ [fixedComputeOffering],
+ [],
+ nonCustomizableProperties.defaultCustomComputeOfferingRestrictions,
+ nonCustomizableProperties.customComputeOfferingHardwareValues,
+ [],
+ updatedVm
+ );
+
+ expect(computeOfferingViewModel.isAvailableByResources).toEqual(true);
+ });
+
+ it('isAvailableByResources should be false, cause unsatisfy resources plus used resources in editing vm',
+ () => {
+ const cpuavailable = '0';
+ const memoryavailable = '0';
+ const limitedAccount: Account = { ...account, memoryavailable, cpuavailable };
+ const updatedVm = { ...vm, memory: '512', cpuNumber: 1 };
+
+ const [computeOfferingViewModel]: ComputeOfferingViewModel[] = getComputeOfferingForVmEditing.projector(
+ limitedAccount,
+ [fixedComputeOffering],
+ [],
+ nonCustomizableProperties.defaultCustomComputeOfferingRestrictions,
+ nonCustomizableProperties.customComputeOfferingHardwareValues,
+ [],
+ updatedVm
+ );
+
+ expect(computeOfferingViewModel.isAvailableByResources).toEqual(false);
+ });
+});
diff --git a/src/app/vm/selectors/view-models/compute-offering-view-model.selector.ts b/src/app/vm/selectors/view-models/compute-offering-view-model.selector.ts
index aeb9630ae9..19b655dee2 100644
--- a/src/app/vm/selectors/view-models/compute-offering-view-model.selector.ts
+++ b/src/app/vm/selectors/view-models/compute-offering-view-model.selector.ts
@@ -10,6 +10,13 @@ import {
import { ComputeOfferingViewModel } from '../../view-models';
import { configSelectors, UserTagsSelectors } from '../../../root-store';
import * as computeOffering from '../../../reducers/service-offerings/redux/service-offerings.reducers';
+import * as fromAuth from '../../../reducers/auth/redux/auth.reducers';
+import * as fromVms from '../../../reducers/vm/redux/vm.reducers';
+
+interface Resources {
+ cpuNumber: number | string;
+ memory: number | string;
+}
const getFixedAndCustomOfferingsArrays = (offerings: ServiceOffering[]) => {
const offeringsArrays = {
@@ -30,7 +37,7 @@ const getCustomOfferingHardwareParameters = (
offering: ServiceOffering,
offeringsParameters: CustomComputeOfferingParameters[]
): CustomComputeOfferingParameters | undefined => {
- return offeringsParameters.find(parameters => parameters.offeringId === offering.id)
+ return offeringsParameters && offeringsParameters.find(parameters => parameters.offeringId === offering.id)
};
const getCustomHardwareValues = (
@@ -71,7 +78,7 @@ const getCustomHardwareRestrictions = (
};
const getHardwareValuesFromTags = (
- serviceOffering: ComputeOfferingViewModel,
+ serviceOffering: ServiceOffering,
tags: Tag[]
): CustomComputeOfferingHardwareValues | null => {
const getValue = (param) => {
@@ -90,59 +97,202 @@ const getHardwareValuesFromTags = (
return null;
};
+const checkAvailabilityForFixedByResources = (
+ cpuNumber: number,
+ memory: number,
+ availableResources: Resources
+): boolean => {
+ const isEnoughCpuNumber = availableResources.cpuNumber === 'Unlimited' || cpuNumber <= availableResources.cpuNumber;
+ const isEnoughMemory = availableResources.memory === 'Unlimited' || memory <= availableResources.memory;
+ return isEnoughCpuNumber && isEnoughMemory;
+};
+
+const checkAvailabilityForCustomByResources = (
+ cpuNumberRestrictions: HardwareLimits,
+ memoryRestrictions: HardwareLimits,
+ availableResources: Resources
+): boolean => {
+ const isEnoughCpuNumber = availableResources.cpuNumber === 'Unlimited'
+ || cpuNumberRestrictions.min <= availableResources.cpuNumber;
+ const isEnoughMemory = availableResources.memory === 'Unlimited'
+ || memoryRestrictions.min <= availableResources.memory;
+ return isEnoughCpuNumber && isEnoughMemory;
+};
+
const getValueThatSatisfiesRestrictions = (defaultValue: number, restrictions: HardwareLimits) => {
if (restrictions.min > defaultValue) {
return restrictions.min;
- } else if (defaultValue > restrictions.max) {
+ }
+ if (defaultValue > restrictions.max) {
return restrictions.max;
}
return defaultValue;
};
-export const getComputeOfferingViewModel = createSelector(
- computeOffering.selectAll,
- configSelectors.get('customComputeOfferingParameters'),
- configSelectors.get('defaultCustomComputeOfferingRestrictions'),
- configSelectors.get('customComputeOfferingHardwareValues'),
- UserTagsSelectors.getServiceOfferingParamTags,
- (
+const getValueThatSatisfiesResources = (defaultValue: number, resourceLimit: string | number): number => {
+ const limit = Number(resourceLimit);
+ if (!isNaN(limit) && limit < defaultValue) {
+ return limit;
+ }
+
+ return defaultValue;
+};
+
+const getRestrictionsThatSatisfiesResources = (
+ restrictions: CustomComputeOfferingHardwareRestrictions,
+ resources: Resources
+): CustomComputeOfferingHardwareRestrictions => {
+ const cpuResource = Number(resources.cpuNumber);
+ const memoryResource = Number(resources.memory);
+ let maxCpuNumber = restrictions.cpunumber.max;
+ if (!isNaN(cpuResource)) {
+ maxCpuNumber = restrictions.cpunumber.max > cpuResource ? cpuResource : restrictions.cpunumber.max;
+ }
+ let maxMemory = restrictions.memory.max;
+ if (!isNaN(memoryResource)) {
+ maxMemory = restrictions.memory.max > memoryResource ? memoryResource : restrictions.memory.max;
+ }
+ return {
+ ...restrictions,
+ cpunumber: {
+ min: restrictions.cpunumber.min,
+ max: maxCpuNumber
+ },
+ memory: {
+ min: restrictions.memory.min,
+ max: maxMemory
+ }
+ };
+};
+
+const getComputeOfferingViewModel = (
offerings,
customComputeOfferingParameters,
defaultRestrictions,
defaultHardwareValues,
- tags
+ tags,
+ availableResources
): ComputeOfferingViewModel[] => {
- const { customOfferings, fixedOfferings } = getFixedAndCustomOfferingsArrays(offerings);
-
- const customOfferingsWithMetadata: ComputeOfferingViewModel[] = customOfferings
- .map((offering: ServiceOffering) => {
- const customParameters = getCustomOfferingHardwareParameters(offering, customComputeOfferingParameters);
- const customHardwareValues = getCustomHardwareValues(customParameters);
- const customHardwareRestrictions = getCustomHardwareRestrictions(customParameters);
- const hardwareValuesFromTags = getHardwareValuesFromTags(offering, tags);
-
- const prioritizedHardwareValues = hardwareValuesFromTags || customHardwareValues || defaultHardwareValues;
- const prioritizedRestrictions = customHardwareRestrictions || defaultRestrictions;
-
- const cpunumber = getValueThatSatisfiesRestrictions(
- prioritizedHardwareValues.cpunumber, prioritizedRestrictions.cpunumber);
- const cpuspeed = getValueThatSatisfiesRestrictions(
- prioritizedHardwareValues.cpuspeed, prioritizedRestrictions.cpuspeed);
- const memory = getValueThatSatisfiesRestrictions(
- prioritizedHardwareValues.memory, prioritizedRestrictions.memory);
-
-
- const offeringViewModel: ComputeOfferingViewModel = {
- ...offering,
- cpunumber,
- cpuspeed,
- memory,
- customOfferingRestrictions: prioritizedRestrictions
- };
- return offeringViewModel;
- });
-
- return [...fixedOfferings, ...customOfferingsWithMetadata];
+ const { customOfferings, fixedOfferings } = getFixedAndCustomOfferingsArrays(offerings);
+
+ const customOfferingsWithMetadata: ComputeOfferingViewModel[] = customOfferings
+ .map((offering: ServiceOffering) => {
+ const customParameters = getCustomOfferingHardwareParameters(offering, customComputeOfferingParameters);
+ const customHardwareValues = getCustomHardwareValues(customParameters);
+ const customHardwareRestrictions = getCustomHardwareRestrictions(customParameters);
+ const hardwareValuesFromTags = getHardwareValuesFromTags(offering, tags);
+
+ const prioritizedHardwareValues = hardwareValuesFromTags || customHardwareValues || defaultHardwareValues;
+ const prioritizedRestrictions = customHardwareRestrictions || defaultRestrictions;
+
+
+ const isAvailableByResources = checkAvailabilityForCustomByResources(
+ prioritizedRestrictions.cpunumber, prioritizedRestrictions.memory, availableResources);
+
+ let cpunumber = getValueThatSatisfiesRestrictions(
+ prioritizedHardwareValues.cpunumber, prioritizedRestrictions.cpunumber);
+ const cpuspeed = getValueThatSatisfiesRestrictions(
+ prioritizedHardwareValues.cpuspeed, prioritizedRestrictions.cpuspeed);
+ let memory = getValueThatSatisfiesRestrictions(
+ prioritizedHardwareValues.memory, prioritizedRestrictions.memory);
+
+ if (isAvailableByResources) {
+ cpunumber = getValueThatSatisfiesResources(cpunumber, availableResources.cpuNumber);
+ memory = getValueThatSatisfiesResources(memory, availableResources.memory);
+ }
+
+ const customOfferingRestrictions = getRestrictionsThatSatisfiesResources(
+ prioritizedRestrictions, availableResources);
+
+ const offeringViewModel: ComputeOfferingViewModel = {
+ ...offering,
+ cpunumber,
+ cpuspeed,
+ memory,
+ customOfferingRestrictions,
+ isAvailableByResources
+ };
+ return offeringViewModel;
+ });
+
+ const fixedOfferingWithMeta = fixedOfferings.map(offering => {
+ const offeringViewModel: ComputeOfferingViewModel = {
+ ...offering,
+ isAvailableByResources: checkAvailabilityForFixedByResources(
+ offering.cpunumber, offering.memory, availableResources)
+ };
+ return offeringViewModel;
+ });
+
+ return [...fixedOfferingWithMeta, ...customOfferingsWithMetadata];
+ };
+
+export const getComputeOfferingForVmEditing = createSelector(
+ fromAuth.getUserAccount,
+ computeOffering.selectAll,
+ configSelectors.get('customComputeOfferingParameters'),
+ configSelectors.get('defaultCustomComputeOfferingRestrictions'),
+ configSelectors.get('customComputeOfferingHardwareValues'),
+ UserTagsSelectors.getServiceOfferingParamTags,
+ fromVms.getSelectedVM,
+ (account,
+ offerings,
+ customComputeOfferingParameters,
+ defaultRestrictions,
+ defaultHardwareValues,
+ tags,
+ vm): ComputeOfferingViewModel[] => {
+ const memoryUsed = vm.memory;
+ const cpuNumberUsed = vm.cpuNumber;
+
+ const cpuNumber = account && account.cpuavailable === 'Unlimited'
+ ? account.cpuavailable
+ : Number(account.cpuavailable) + cpuNumberUsed;
+ const memory = account && account.memoryavailable === 'Unlimited'
+ ? account.memoryavailable
+ : Number(account.memoryavailable) + memoryUsed;
+
+ const availableResources: Resources = { cpuNumber, memory };
+
+ return getComputeOfferingViewModel(
+ offerings,
+ customComputeOfferingParameters,
+ defaultRestrictions,
+ defaultHardwareValues,
+ tags,
+ availableResources);
+ }
+);
+
+export const getComputeOfferingForVmCreation = createSelector(
+ fromAuth.getUserAccount,
+ computeOffering.selectAll,
+ configSelectors.get('customComputeOfferingParameters'),
+ configSelectors.get('defaultCustomComputeOfferingRestrictions'),
+ configSelectors.get('customComputeOfferingHardwareValues'),
+ UserTagsSelectors.getServiceOfferingParamTags,
+ (account,
+ offerings,
+ customComputeOfferingParameters,
+ defaultRestrictions,
+ defaultHardwareValues,
+ tags): ComputeOfferingViewModel[] => {
+
+ /**
+ * '0' used to prevent an error when account is not loaded yet
+ * it happened when you go to vm creation dialog by url
+ */
+ const availableResources: Resources = {
+ cpuNumber: account && account.cpuavailable || '0',
+ memory: account && account.memoryavailable || '0'
+ };
+ return getComputeOfferingViewModel(
+ offerings,
+ customComputeOfferingParameters,
+ defaultRestrictions,
+ defaultHardwareValues,
+ tags,
+ availableResources);
}
);
diff --git a/src/app/vm/selectors/view-models/index.ts b/src/app/vm/selectors/view-models/index.ts
index 65a8ed8a70..470eaeb895 100644
--- a/src/app/vm/selectors/view-models/index.ts
+++ b/src/app/vm/selectors/view-models/index.ts
@@ -1 +1,4 @@
-export { getComputeOfferingViewModel } from './compute-offering-view-model.selector';
+export {
+ getComputeOfferingForVmCreation,
+ getComputeOfferingForVmEditing
+} from './compute-offering-view-model.selector';
diff --git a/src/app/vm/view-models/compute-offering.view-model.ts b/src/app/vm/view-models/compute-offering.view-model.ts
index 329cf697bf..a9122b5eac 100644
--- a/src/app/vm/view-models/compute-offering.view-model.ts
+++ b/src/app/vm/view-models/compute-offering.view-model.ts
@@ -1,5 +1,6 @@
import { CustomComputeOfferingHardwareRestrictions, ServiceOffering } from '../../shared/models';
export interface ComputeOfferingViewModel extends ServiceOffering {
+ isAvailableByResources: boolean;
customOfferingRestrictions?: CustomComputeOfferingHardwareRestrictions;
}
diff --git a/src/app/vm/vm-creation/components/service-offering-selector/service-offering-selector.component.html b/src/app/vm/vm-creation/components/service-offering-selector/service-offering-selector.component.html
index 784a5c83d8..f3247023f6 100644
--- a/src/app/vm/vm-creation/components/service-offering-selector/service-offering-selector.component.html
+++ b/src/app/vm/vm-creation/components/service-offering-selector/service-offering-selector.component.html
@@ -14,6 +14,9 @@