@rancher/shell 3.0.9-rc.5 → 3.0.9-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/images/providers/oci-open-containers.svg +22 -0
- package/assets/images/providers/traefik.png +0 -0
- package/assets/styles/themes/_dark.scss +2 -0
- package/assets/styles/themes/_light.scss +2 -0
- package/assets/styles/themes/_modern.scss +6 -0
- package/assets/translations/en-us.yaml +129 -25
- package/components/CruResource.vue +3 -1
- package/components/ExplorerProjectsNamespaces.vue +12 -12
- package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +109 -0
- package/components/Resource/Detail/Card/StatusCard/index.vue +21 -4
- package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +19 -2
- package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +19 -11
- package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +12 -0
- package/components/Resource/Detail/ResourcePopover/index.vue +2 -0
- package/components/Resource/Detail/ResourceRow.vue +2 -2
- package/components/ResourceList/index.vue +7 -4
- package/components/Window/ContainerLogs.vue +48 -37
- package/components/fleet/FleetClusterTargets/TargetsList.vue +2 -2
- package/components/fleet/FleetClusterTargets/index.vue +6 -1
- package/components/fleet/GitRepoAdvancedTab.vue +333 -0
- package/components/fleet/GitRepoMetadataTab.vue +43 -0
- package/components/fleet/GitRepoRepositoryTab.vue +101 -0
- package/components/fleet/GitRepoTargetTab.vue +77 -0
- package/components/fleet/HelmOpAdvancedTab.vue +247 -0
- package/components/fleet/HelmOpChartTab.vue +158 -0
- package/components/fleet/HelmOpMetadataTab.vue +46 -0
- package/components/fleet/HelmOpTargetTab.vue +84 -0
- package/components/fleet/HelmOpValuesTab.vue +147 -0
- package/components/fleet/__tests__/FleetClusterTargets.test.ts +119 -70
- package/components/form/NodeScheduling.vue +81 -7
- package/components/form/PodAffinity.vue +1 -36
- package/components/form/ResourceLabeledSelect.vue +8 -4
- package/components/form/ResourceQuota/Namespace.vue +30 -9
- package/components/form/ResourceQuota/NamespaceRow.vue +25 -7
- package/components/form/ResourceQuota/Project.vue +140 -82
- package/components/form/ResourceQuota/ResourceQuotaEntry.vue +145 -0
- package/components/form/ResourceQuota/__tests__/Namespace.test.ts +307 -0
- package/components/form/ResourceQuota/__tests__/NamespaceRow.test.ts +281 -0
- package/components/form/ResourceQuota/__tests__/Project.test.ts +274 -27
- package/components/form/ResourceQuota/__tests__/ResourceQuotaEntry.test.ts +215 -0
- package/components/form/SchedulingCustomization.vue +14 -6
- package/components/form/SelectOrCreateAuthSecret.vue +107 -18
- package/components/form/__tests__/NodeScheduling.test.ts +12 -9
- package/components/form/__tests__/PodAffinity.test.ts +21 -2
- package/components/form/__tests__/SchedulingCustomization.test.ts +240 -0
- package/components/formatter/ClusterLink.vue +8 -0
- package/components/formatter/SecretOrigin.vue +79 -0
- package/config/labels-annotations.js +7 -6
- package/config/pagination-table-headers.js +6 -4
- package/config/product/explorer.js +1 -11
- package/config/query-params.js +3 -0
- package/config/settings.ts +15 -2
- package/config/table-headers.js +21 -17
- package/config/types.js +23 -8
- package/detail/workload/index.vue +11 -16
- package/dialog/DeactivateDriverDialog.vue +1 -1
- package/dialog/Ipv6NetworkingDialog.vue +156 -0
- package/dialog/ScalePoolDownDialog.vue +2 -2
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -1
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +1 -0
- package/edit/__tests__/management.cattle.io.project.test.js +56 -128
- package/edit/auth/oidc.vue +1 -1
- package/edit/catalog.cattle.io.clusterrepo.vue +155 -25
- package/edit/fleet.cattle.io.gitrepo.vue +153 -283
- package/edit/fleet.cattle.io.helmop.vue +190 -332
- package/edit/management.cattle.io.project.vue +5 -42
- package/edit/management.cattle.io.setting.vue +6 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +55 -24
- package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +1 -103
- package/edit/provisioning.cattle.io.cluster/__tests__/index.test.ts +13 -1
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2-fleet-cluster-agent.test.ts +283 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +65 -49
- package/edit/provisioning.cattle.io.cluster/ingress/IngressCards.vue +112 -0
- package/edit/provisioning.cattle.io.cluster/ingress/IngressConfiguration.vue +158 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +171 -72
- package/edit/provisioning.cattle.io.cluster/shared.ts +36 -1
- package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +2 -1
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +55 -7
- package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +319 -0
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +2 -1
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/__tests__/S3Config.test.ts +13 -1
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +10 -44
- package/edit/secret/index.vue +1 -1
- package/edit/token.vue +68 -29
- package/edit/workload/__tests__/index.test.ts +2 -37
- package/edit/workload/index.vue +6 -2
- package/edit/workload/mixins/workload.js +0 -32
- package/list/__tests__/management.cattle.io.setting.test.ts +198 -0
- package/list/management.cattle.io.setting.vue +13 -0
- package/list/provisioning.cattle.io.cluster.vue +50 -1
- package/list/secret.vue +4 -9
- package/list/service.vue +6 -8
- package/machine-config/amazonec2.vue +11 -4
- package/machine-config/components/EC2Networking.vue +46 -30
- package/machine-config/components/__tests__/EC2Networking.test.ts +7 -7
- package/machine-config/components/__tests__/utils/vpcSubnetMockData.js +0 -9
- package/machine-config/digitalocean.vue +3 -3
- package/models/__tests__/namespace.test.ts +11 -0
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +96 -0
- package/models/__tests__/workload.test.ts +42 -1
- package/models/catalog.cattle.io.clusterrepo.js +30 -4
- package/models/ext.cattle.io.token.js +48 -0
- package/models/kontainerdriver.js +2 -2
- package/models/namespace.js +7 -1
- package/models/nodedriver.js +2 -2
- package/models/provisioning.cattle.io.cluster.js +28 -7
- package/models/secret.js +0 -17
- package/models/service.js +44 -1
- package/models/token.js +4 -0
- package/models/workload.js +12 -6
- package/package.json +1 -1
- package/pages/account/index.vue +96 -67
- package/pages/auth/setup.vue +5 -14
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +4 -1
- package/pages/c/_cluster/apps/charts/index.vue +93 -4
- package/pages/c/_cluster/apps/charts/install.vue +317 -42
- package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -4
- package/pages/c/_cluster/settings/index.vue +3 -1
- package/plugins/dashboard-store/__tests__/getters.test.ts +108 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +27 -0
- package/plugins/dashboard-store/actions.js +3 -8
- package/plugins/dashboard-store/getters.js +7 -5
- package/plugins/dashboard-store/mutations.js +4 -1
- package/plugins/dashboard-store/resource-class.js +3 -3
- package/plugins/steve/__tests__/steve-class.test.ts +102 -141
- package/plugins/steve/steve-class.js +12 -3
- package/plugins/steve/steve-pagination-utils.ts +6 -2
- package/rancher-components/RcIcon/types.ts +2 -0
- package/rancher-components/RcItemCard/RcItemCard.vue +64 -19
- package/store/prefs.js +3 -0
- package/types/aws-sdk.d.ts +121 -0
- package/types/resources/node.ts +15 -0
- package/types/shell/index.d.ts +536 -506
- package/types/store/pagination.types.ts +5 -5
- package/utils/__tests__/array.test.ts +1 -29
- package/utils/__tests__/cluster-agent-configuration.test.ts +203 -0
- package/utils/array.ts +0 -11
- package/utils/aws.ts +21 -0
- package/utils/cluster.js +22 -2
- package/utils/selector-typed.ts +1 -1
- package/components/__tests__/ProjectRow.test.ts +0 -206
- package/components/form/ResourceQuota/ProjectRow.vue +0 -277
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import NamespaceRow from '@shell/components/form/ResourceQuota/NamespaceRow.vue';
|
|
3
|
+
|
|
4
|
+
describe('namespaceRow', () => {
|
|
5
|
+
const storeMock = { getters: { 'i18n/t': (key: string) => key } };
|
|
6
|
+
|
|
7
|
+
const standardType = {
|
|
8
|
+
value: 'limitsCpu',
|
|
9
|
+
inputExponent: -1,
|
|
10
|
+
baseUnit: '',
|
|
11
|
+
placeholder: 'Enter CPU',
|
|
12
|
+
increment: 1,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const extendedType = {
|
|
16
|
+
value: 'extended',
|
|
17
|
+
inputExponent: 0,
|
|
18
|
+
baseUnit: '',
|
|
19
|
+
placeholder: 'Enter value',
|
|
20
|
+
increment: 1,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const defaultProps = {
|
|
24
|
+
mode: 'edit',
|
|
25
|
+
type: 'limitsCpu',
|
|
26
|
+
types: [standardType, extendedType],
|
|
27
|
+
value: { limit: { limitsCpu: '1000m' } },
|
|
28
|
+
namespace: { id: 'test-ns' },
|
|
29
|
+
projectResourceQuotaLimits: { limitsCpu: '2000m' },
|
|
30
|
+
namespaceResourceQuotaLimits: [],
|
|
31
|
+
defaultResourceQuotaLimits: { limitsCpu: '500m' },
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const createWrapper = (propsOverrides: Record<string, unknown> = {}) => {
|
|
35
|
+
return shallowMount(NamespaceRow, {
|
|
36
|
+
props: { ...defaultProps, ...propsOverrides },
|
|
37
|
+
global: { mocks: { $store: storeMock } },
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
describe('computed: displayType', () => {
|
|
42
|
+
it('returns the type unchanged for standard quota types', () => {
|
|
43
|
+
const wrapper: any = createWrapper({ type: 'limitsCpu' });
|
|
44
|
+
|
|
45
|
+
expect(wrapper.vm.displayType).toBe('limitsCpu');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('strips the "extended." prefix for extended quota types', () => {
|
|
49
|
+
const wrapper: any = createWrapper({
|
|
50
|
+
type: 'extended.requests.nvidia.com/gpu',
|
|
51
|
+
value: { limit: { extended: { 'requests.nvidia.com/gpu': '4' } } },
|
|
52
|
+
projectResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '8' },
|
|
53
|
+
defaultResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '2' },
|
|
54
|
+
namespaceResourceQuotaLimits: [],
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(wrapper.vm.displayType).toBe('requests.nvidia.com/gpu');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('strips only the leading "extended." when the resource identifier itself starts with "extended."', () => {
|
|
61
|
+
const wrapper: any = createWrapper({
|
|
62
|
+
type: 'extended.extended.nvidia.com/gpu',
|
|
63
|
+
value: { limit: { extended: { 'extended.nvidia.com/gpu': '4' } } },
|
|
64
|
+
projectResourceQuotaLimits: { 'extended.extended.nvidia.com/gpu': '8' },
|
|
65
|
+
defaultResourceQuotaLimits: { 'extended.extended.nvidia.com/gpu': '2' },
|
|
66
|
+
namespaceResourceQuotaLimits: [],
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect(wrapper.vm.displayType).toBe('extended.nvidia.com/gpu');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('computed: currentLimit', () => {
|
|
74
|
+
it('returns the limit value for a standard quota type', () => {
|
|
75
|
+
const wrapper: any = createWrapper({
|
|
76
|
+
type: 'limitsCpu',
|
|
77
|
+
value: { limit: { limitsCpu: '1000m' } },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
expect(wrapper.vm.currentLimit).toBe('1000m');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('returns undefined when the standard type key is absent from limit', () => {
|
|
84
|
+
const wrapper: any = createWrapper({
|
|
85
|
+
type: 'limitsCpu',
|
|
86
|
+
value: { limit: {} },
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(wrapper.vm.currentLimit).toBeUndefined();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('reads the value from limit.extended for an extended quota type', () => {
|
|
93
|
+
const wrapper: any = createWrapper({
|
|
94
|
+
type: 'extended.requests.nvidia.com/gpu',
|
|
95
|
+
value: { limit: { extended: { 'requests.nvidia.com/gpu': '4' } } },
|
|
96
|
+
projectResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '8' },
|
|
97
|
+
defaultResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '2' },
|
|
98
|
+
namespaceResourceQuotaLimits: [],
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(wrapper.vm.currentLimit).toBe('4');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('returns undefined when limit.extended is absent for an extended quota type', () => {
|
|
105
|
+
const wrapper: any = createWrapper({
|
|
106
|
+
type: 'extended.requests.nvidia.com/gpu',
|
|
107
|
+
value: { limit: {} },
|
|
108
|
+
projectResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '8' },
|
|
109
|
+
defaultResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '2' },
|
|
110
|
+
namespaceResourceQuotaLimits: [],
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expect(wrapper.vm.currentLimit).toBeUndefined();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('reads the correct value when the resource identifier itself starts with "extended."', () => {
|
|
117
|
+
const wrapper: any = createWrapper({
|
|
118
|
+
type: 'extended.extended.nvidia.com/gpu',
|
|
119
|
+
value: { limit: { extended: { 'extended.nvidia.com/gpu': '4' } } },
|
|
120
|
+
projectResourceQuotaLimits: { 'extended.extended.nvidia.com/gpu': '8' },
|
|
121
|
+
defaultResourceQuotaLimits: { 'extended.extended.nvidia.com/gpu': '2' },
|
|
122
|
+
namespaceResourceQuotaLimits: [],
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
expect(wrapper.vm.currentLimit).toBe('4');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('returns undefined when the extended resource identifier is not in limit.extended', () => {
|
|
129
|
+
const wrapper: any = createWrapper({
|
|
130
|
+
type: 'extended.requests.nvidia.com/gpu',
|
|
131
|
+
value: { limit: { extended: { 'example.com/fpga': '1' } } },
|
|
132
|
+
projectResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '8' },
|
|
133
|
+
defaultResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '2' },
|
|
134
|
+
namespaceResourceQuotaLimits: [],
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(wrapper.vm.currentLimit).toBeUndefined();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it.each([
|
|
141
|
+
['limitsCpu', { limitsCpu: '500m' }, '500m'],
|
|
142
|
+
['configMaps', { configMaps: '10' }, '10'],
|
|
143
|
+
['pods', { pods: '20' }, '20'],
|
|
144
|
+
])('returns the correct value for standard type "%s"', (type, limit, expected) => {
|
|
145
|
+
const wrapper: any = createWrapper({
|
|
146
|
+
type,
|
|
147
|
+
types: [
|
|
148
|
+
{
|
|
149
|
+
value: type, inputExponent: 0, baseUnit: '', placeholder: '', increment: 1
|
|
150
|
+
},
|
|
151
|
+
extendedType,
|
|
152
|
+
],
|
|
153
|
+
value: { limit },
|
|
154
|
+
projectResourceQuotaLimits: { [type]: '100' },
|
|
155
|
+
defaultResourceQuotaLimits: { [type]: '0' },
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
expect(wrapper.vm.currentLimit).toBe(expected);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('mounted(): initial value emission', () => {
|
|
163
|
+
it('emits update:value on mount with the type as the first argument', () => {
|
|
164
|
+
const wrapper: any = createWrapper({
|
|
165
|
+
type: 'limitsCpu',
|
|
166
|
+
value: { limit: { limitsCpu: '1000m' } },
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const emitted = wrapper.emitted('update:value');
|
|
170
|
+
|
|
171
|
+
expect(emitted).toBeTruthy();
|
|
172
|
+
expect(emitted![0][0]).toBe('limitsCpu');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('emits update:value on mount when currentLimit is not set, using defaultResourceQuotaLimits', () => {
|
|
176
|
+
const wrapper: any = createWrapper({
|
|
177
|
+
type: 'limitsCpu',
|
|
178
|
+
value: { limit: {} },
|
|
179
|
+
projectResourceQuotaLimits: { limitsCpu: '2000m' },
|
|
180
|
+
defaultResourceQuotaLimits: { limitsCpu: '500m' },
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const emitted = wrapper.emitted('update:value');
|
|
184
|
+
|
|
185
|
+
expect(emitted).toBeTruthy();
|
|
186
|
+
expect(emitted![0][0]).toBe('limitsCpu');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('emits update:value on mount for an extended quota type', () => {
|
|
190
|
+
const wrapper: any = createWrapper({
|
|
191
|
+
type: 'extended.requests.nvidia.com/gpu',
|
|
192
|
+
value: { limit: { extended: { 'requests.nvidia.com/gpu': '4' } } },
|
|
193
|
+
projectResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '8' },
|
|
194
|
+
defaultResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '2' },
|
|
195
|
+
namespaceResourceQuotaLimits: [],
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const emitted = wrapper.emitted('update:value');
|
|
199
|
+
|
|
200
|
+
expect(emitted).toBeTruthy();
|
|
201
|
+
expect(emitted![0][0]).toBe('extended.requests.nvidia.com/gpu');
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('computed: totalContribution', () => {
|
|
206
|
+
it('equals zero when currentLimit is 0 and no other namespaces contribute', () => {
|
|
207
|
+
const wrapper: any = createWrapper({
|
|
208
|
+
type: 'limitsCpu',
|
|
209
|
+
value: { limit: { limitsCpu: '0' } },
|
|
210
|
+
projectResourceQuotaLimits: { limitsCpu: '2000m' },
|
|
211
|
+
namespaceResourceQuotaLimits: [],
|
|
212
|
+
defaultResourceQuotaLimits: { limitsCpu: '0' },
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
expect(wrapper.vm.totalContribution).toBe(0);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('includes contributions from other namespaces alongside the current limit', () => {
|
|
219
|
+
// ns2 contributes 500m, current namespace has 0
|
|
220
|
+
const wrapper: any = createWrapper({
|
|
221
|
+
type: 'limitsCpu',
|
|
222
|
+
value: { limit: { limitsCpu: '0' } },
|
|
223
|
+
projectResourceQuotaLimits: { limitsCpu: '2000m' },
|
|
224
|
+
namespaceResourceQuotaLimits: [
|
|
225
|
+
{ id: 'ns2', limitsCpu: '500m' },
|
|
226
|
+
],
|
|
227
|
+
defaultResourceQuotaLimits: { limitsCpu: '0' },
|
|
228
|
+
namespace: { id: 'test-ns' },
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// ns2's 500m contribution should be included
|
|
232
|
+
expect(wrapper.vm.totalContribution).toBeGreaterThan(0);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('uses currentLimit for the extended type in the total', () => {
|
|
236
|
+
const wrapper: any = createWrapper({
|
|
237
|
+
type: 'extended.requests.nvidia.com/gpu',
|
|
238
|
+
value: { limit: { extended: { 'requests.nvidia.com/gpu': '0' } } },
|
|
239
|
+
projectResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '8' },
|
|
240
|
+
namespaceResourceQuotaLimits: [],
|
|
241
|
+
defaultResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '0' },
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
expect(wrapper.vm.totalContribution).toBe(0);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
describe('computed: namespaceLimits', () => {
|
|
249
|
+
it('excludes the current namespace from the list', () => {
|
|
250
|
+
const wrapper: any = createWrapper({
|
|
251
|
+
type: 'limitsCpu',
|
|
252
|
+
value: { limit: { limitsCpu: '500m' } },
|
|
253
|
+
projectResourceQuotaLimits: { limitsCpu: '2000m' },
|
|
254
|
+
namespaceResourceQuotaLimits: [
|
|
255
|
+
{ id: 'test-ns', limitsCpu: '500m' },
|
|
256
|
+
{ id: 'other-ns', limitsCpu: '300m' },
|
|
257
|
+
],
|
|
258
|
+
namespace: { id: 'test-ns' },
|
|
259
|
+
defaultResourceQuotaLimits: { limitsCpu: '0' },
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// Only the other namespace should contribute
|
|
263
|
+
expect(wrapper.vm.namespaceLimits).toHaveLength(1);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('handles flattened extended keys from namespaceResourceQuotaLimits', () => {
|
|
267
|
+
const wrapper: any = createWrapper({
|
|
268
|
+
type: 'extended.requests.nvidia.com/gpu',
|
|
269
|
+
value: { limit: { extended: { 'requests.nvidia.com/gpu': '1' } } },
|
|
270
|
+
projectResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '8' },
|
|
271
|
+
namespaceResourceQuotaLimits: [
|
|
272
|
+
{ id: 'other-ns', 'extended.requests.nvidia.com/gpu': '2' },
|
|
273
|
+
],
|
|
274
|
+
namespace: { id: 'test-ns' },
|
|
275
|
+
defaultResourceQuotaLimits: { 'extended.requests.nvidia.com/gpu': '0' },
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
expect(wrapper.vm.namespaceLimits).toHaveLength(1);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
});
|
|
@@ -14,50 +14,297 @@ describe('project', () => {
|
|
|
14
14
|
types: []
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
describe('created() spec parser', () => {
|
|
18
|
+
it('should initialise resourceQuotas as an empty array when spec limits are empty', () => {
|
|
19
|
+
const wrapper: any = shallowMount(Project, { props: defaultProps });
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
expect(wrapper.vm.resourceQuotas).toStrictEqual([]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should parse a standard resource type from the spec into resourceQuotas', () => {
|
|
25
|
+
const wrapper: any = shallowMount(Project, {
|
|
26
|
+
props: {
|
|
27
|
+
...defaultProps,
|
|
28
|
+
value: {
|
|
29
|
+
spec: {
|
|
30
|
+
resourceQuota: { limit: { configMaps: '20' } },
|
|
31
|
+
namespaceDefaultResourceQuota: { limit: { configMaps: '10' } }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
expect(wrapper.vm.resourceQuotas).toHaveLength(1);
|
|
38
|
+
expect(wrapper.vm.resourceQuotas[0]).toMatchObject({
|
|
39
|
+
resourceType: 'configMaps',
|
|
40
|
+
resourceIdentifier: 'configMaps',
|
|
41
|
+
projectLimit: '20',
|
|
42
|
+
namespaceDefaultLimit: '10',
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should parse an extended resource type from the spec into resourceQuotas', () => {
|
|
47
|
+
const wrapper: any = shallowMount(Project, {
|
|
48
|
+
props: {
|
|
49
|
+
...defaultProps,
|
|
50
|
+
value: {
|
|
51
|
+
spec: {
|
|
52
|
+
resourceQuota: { limit: { extended: { test1: '10' } } },
|
|
53
|
+
namespaceDefaultResourceQuota: { limit: { extended: { test1: '5' } } }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(wrapper.vm.resourceQuotas).toHaveLength(1);
|
|
60
|
+
expect(wrapper.vm.resourceQuotas[0]).toMatchObject({
|
|
61
|
+
resourceType: TYPES.EXTENDED,
|
|
62
|
+
resourceIdentifier: 'test1',
|
|
63
|
+
projectLimit: '10',
|
|
64
|
+
namespaceDefaultLimit: '5',
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should initialise resourceQuotas as an empty array when resourceQuota exists but has no limit property', () => {
|
|
69
|
+
const wrapper: any = shallowMount(Project, {
|
|
70
|
+
props: {
|
|
71
|
+
...defaultProps,
|
|
72
|
+
value: {
|
|
73
|
+
spec: {
|
|
74
|
+
resourceQuota: {},
|
|
75
|
+
namespaceDefaultResourceQuota: { limit: {} }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(wrapper.vm.resourceQuotas).toStrictEqual([]);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should parse project quotas and fall back to empty string for missing ns limits when namespaceDefaultResourceQuota has no limit property', () => {
|
|
85
|
+
const wrapper: any = shallowMount(Project, {
|
|
86
|
+
props: {
|
|
87
|
+
...defaultProps,
|
|
88
|
+
value: {
|
|
89
|
+
spec: {
|
|
90
|
+
resourceQuota: { limit: { configMaps: '20' } },
|
|
91
|
+
namespaceDefaultResourceQuota: {}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
21
96
|
|
|
22
|
-
|
|
23
|
-
|
|
97
|
+
expect(wrapper.vm.resourceQuotas).toHaveLength(1);
|
|
98
|
+
expect(wrapper.vm.resourceQuotas[0]).toMatchObject({
|
|
99
|
+
resourceType: 'configMaps',
|
|
100
|
+
projectLimit: '20',
|
|
101
|
+
namespaceDefaultLimit: '',
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should parse both standard and extended resource types into resourceQuotas', () => {
|
|
106
|
+
const wrapper: any = shallowMount(Project, {
|
|
107
|
+
props: {
|
|
108
|
+
...defaultProps,
|
|
109
|
+
value: {
|
|
110
|
+
spec: {
|
|
111
|
+
resourceQuota: { limit: { configMaps: '20', extended: { test1: '10' } } },
|
|
112
|
+
namespaceDefaultResourceQuota: { limit: { configMaps: '15', extended: { test1: '8' } } }
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(wrapper.vm.resourceQuotas).toHaveLength(2);
|
|
119
|
+
});
|
|
24
120
|
});
|
|
25
121
|
|
|
26
|
-
|
|
27
|
-
|
|
122
|
+
describe('specFromQuotas()', () => {
|
|
123
|
+
it('should convert a standard resourceQuota entry to projectLimit and nsLimit', () => {
|
|
124
|
+
const wrapper: any = shallowMount(Project, { props: defaultProps });
|
|
125
|
+
|
|
126
|
+
wrapper.setData({
|
|
127
|
+
resourceQuotas: [{
|
|
128
|
+
id: '1',
|
|
129
|
+
resourceType: 'configMaps',
|
|
130
|
+
resourceIdentifier: 'configMaps',
|
|
131
|
+
projectLimit: '20',
|
|
132
|
+
namespaceDefaultLimit: '10',
|
|
133
|
+
}]
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
expect(wrapper.vm.specFromQuotas()).toStrictEqual({
|
|
137
|
+
projectLimit: { configMaps: '20' },
|
|
138
|
+
nsLimit: { configMaps: '10' },
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should convert an extended resourceQuota entry to a nested extended object', () => {
|
|
143
|
+
const wrapper: any = shallowMount(Project, { props: defaultProps });
|
|
144
|
+
|
|
145
|
+
wrapper.setData({
|
|
146
|
+
resourceQuotas: [{
|
|
147
|
+
id: '1',
|
|
148
|
+
resourceType: TYPES.EXTENDED,
|
|
149
|
+
resourceIdentifier: 'my-resource',
|
|
150
|
+
projectLimit: '5',
|
|
151
|
+
namespaceDefaultLimit: '3',
|
|
152
|
+
}]
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(wrapper.vm.specFromQuotas()).toStrictEqual({
|
|
156
|
+
projectLimit: { extended: { 'my-resource': '5' } },
|
|
157
|
+
nsLimit: { extended: { 'my-resource': '3' } },
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should omit an extended entry from the spec when resourceIdentifier is empty', () => {
|
|
162
|
+
const wrapper: any = shallowMount(Project, { props: defaultProps });
|
|
28
163
|
|
|
29
|
-
|
|
164
|
+
wrapper.setData({
|
|
165
|
+
resourceQuotas: [{
|
|
166
|
+
id: '1',
|
|
167
|
+
resourceType: TYPES.EXTENDED,
|
|
168
|
+
resourceIdentifier: '',
|
|
169
|
+
projectLimit: '5',
|
|
170
|
+
namespaceDefaultLimit: '3',
|
|
171
|
+
}]
|
|
172
|
+
});
|
|
30
173
|
|
|
31
|
-
|
|
174
|
+
const { projectLimit } = wrapper.vm.specFromQuotas();
|
|
32
175
|
|
|
33
|
-
|
|
34
|
-
|
|
176
|
+
expect(projectLimit.extended).toBeUndefined();
|
|
177
|
+
});
|
|
35
178
|
});
|
|
36
179
|
|
|
37
|
-
|
|
38
|
-
const
|
|
180
|
+
describe('remainingTypes()', () => {
|
|
181
|
+
const typesProps = [
|
|
182
|
+
{
|
|
183
|
+
value: TYPES.EXTENDED,
|
|
184
|
+
inputExponent: 0,
|
|
185
|
+
baseUnit: '',
|
|
186
|
+
labelKey: 'resourceQuota.custom',
|
|
187
|
+
placeholderKey: 'resourceQuota.projectLimit.unitlessPlaceholder'
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
value: 'configMaps',
|
|
191
|
+
inputExponent: 0,
|
|
192
|
+
baseUnit: '',
|
|
193
|
+
labelKey: 'resourceQuota.configMaps',
|
|
194
|
+
placeholderKey: 'resourceQuota.projectLimit.unitlessPlaceholder'
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
value: 'pods',
|
|
198
|
+
inputExponent: 0,
|
|
199
|
+
baseUnit: '',
|
|
200
|
+
labelKey: 'resourceQuota.pods',
|
|
201
|
+
placeholderKey: 'resourceQuota.projectLimit.unitlessPlaceholder'
|
|
202
|
+
}
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
it('should exclude already-used non-extended types', () => {
|
|
206
|
+
const wrapper: any = shallowMount(Project, { props: { ...defaultProps, types: typesProps } });
|
|
207
|
+
|
|
208
|
+
wrapper.setData({
|
|
209
|
+
resourceQuotas: [{
|
|
210
|
+
id: '1',
|
|
211
|
+
resourceType: 'configMaps',
|
|
212
|
+
resourceIdentifier: 'configMaps',
|
|
213
|
+
projectLimit: '20',
|
|
214
|
+
namespaceDefaultLimit: '10',
|
|
215
|
+
}]
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const values = wrapper.vm.remainingTypes('pods').map((t: any) => t.value);
|
|
219
|
+
|
|
220
|
+
expect(values).not.toContain('configMaps');
|
|
221
|
+
expect(values).toContain('pods');
|
|
222
|
+
expect(values).toContain(TYPES.EXTENDED);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should include the currentType even if it is already used', () => {
|
|
226
|
+
const wrapper: any = shallowMount(Project, { props: { ...defaultProps, types: typesProps } });
|
|
227
|
+
|
|
228
|
+
wrapper.setData({
|
|
229
|
+
resourceQuotas: [{
|
|
230
|
+
id: '1',
|
|
231
|
+
resourceType: 'configMaps',
|
|
232
|
+
resourceIdentifier: 'configMaps',
|
|
233
|
+
projectLimit: '20',
|
|
234
|
+
namespaceDefaultLimit: '10',
|
|
235
|
+
}]
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const values = wrapper.vm.remainingTypes('configMaps').map((t: any) => t.value);
|
|
239
|
+
|
|
240
|
+
expect(values).toContain('configMaps');
|
|
241
|
+
});
|
|
39
242
|
|
|
40
|
-
|
|
243
|
+
it('should always include extended type', () => {
|
|
244
|
+
const wrapper: any = shallowMount(Project, { props: { ...defaultProps, types: typesProps } });
|
|
41
245
|
|
|
42
|
-
|
|
246
|
+
const values = wrapper.vm.remainingTypes('pods').map((t: any) => t.value);
|
|
43
247
|
|
|
44
|
-
|
|
45
|
-
|
|
248
|
+
expect(values).toContain(TYPES.EXTENDED);
|
|
249
|
+
});
|
|
46
250
|
});
|
|
47
251
|
|
|
48
|
-
|
|
49
|
-
|
|
252
|
+
describe('validationChanged watcher', () => {
|
|
253
|
+
it('should emit validationChanged with false when an extended quota has no resource identifier', async() => {
|
|
254
|
+
const wrapper: any = shallowMount(Project, { props: defaultProps });
|
|
255
|
+
|
|
256
|
+
await wrapper.setData({
|
|
257
|
+
resourceQuotas: [{
|
|
258
|
+
id: '1',
|
|
259
|
+
resourceType: TYPES.EXTENDED,
|
|
260
|
+
resourceIdentifier: '',
|
|
261
|
+
projectLimit: '5',
|
|
262
|
+
namespaceDefaultLimit: '3',
|
|
263
|
+
}]
|
|
264
|
+
});
|
|
50
265
|
|
|
51
|
-
|
|
266
|
+
const emitted = wrapper.emitted('validationChanged');
|
|
52
267
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
customType: 'my-resource',
|
|
56
|
-
index: 0
|
|
268
|
+
expect(emitted).toBeTruthy();
|
|
269
|
+
expect(emitted[emitted.length - 1]).toStrictEqual([false]);
|
|
57
270
|
});
|
|
58
271
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
272
|
+
it('should emit validationChanged with true when all extended quotas have resource identifiers', async() => {
|
|
273
|
+
const wrapper: any = shallowMount(Project, { props: defaultProps });
|
|
274
|
+
|
|
275
|
+
await wrapper.setData({
|
|
276
|
+
resourceQuotas: [{
|
|
277
|
+
id: '1',
|
|
278
|
+
resourceType: TYPES.EXTENDED,
|
|
279
|
+
resourceIdentifier: 'my-resource',
|
|
280
|
+
projectLimit: '5',
|
|
281
|
+
namespaceDefaultLimit: '3',
|
|
282
|
+
}]
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const emitted = wrapper.emitted('validationChanged');
|
|
286
|
+
|
|
287
|
+
expect(emitted).toBeTruthy();
|
|
288
|
+
expect(emitted[emitted.length - 1]).toStrictEqual([true]);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('should emit validationChanged with true when resourceQuotas has no extended entries', async() => {
|
|
292
|
+
const wrapper: any = shallowMount(Project, { props: defaultProps });
|
|
293
|
+
|
|
294
|
+
await wrapper.setData({
|
|
295
|
+
resourceQuotas: [{
|
|
296
|
+
id: '1',
|
|
297
|
+
resourceType: 'configMaps',
|
|
298
|
+
resourceIdentifier: 'configMaps',
|
|
299
|
+
projectLimit: '20',
|
|
300
|
+
namespaceDefaultLimit: '10',
|
|
301
|
+
}]
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const emitted = wrapper.emitted('validationChanged');
|
|
305
|
+
|
|
306
|
+
expect(emitted).toBeTruthy();
|
|
307
|
+
expect(emitted[emitted.length - 1]).toStrictEqual([true]);
|
|
308
|
+
});
|
|
62
309
|
});
|
|
63
310
|
});
|