@spinnaker/google 2026.3.1 → 2026.4.0
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/dist/autoscalingPolicy/GceAutoScalingFieldLayout.d.ts +2 -2
- package/dist/domain/loadBalancer.d.ts +6 -3
- package/dist/image/ImageSelect.d.ts +1 -1
- package/dist/index.js +159 -1
- package/dist/index.js.map +1 -1
- package/dist/loadBalancer/configure/common/gceLoadBalancerModels.d.ts +13 -2
- package/dist/loadBalancer/configure/http/GceHttpLoadBalancerEditor.d.ts +3 -0
- package/dist/loadBalancer/configure/http/GceHttpLoadBalancerModal.d.ts +1 -1
- package/dist/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerEditor.d.ts +30 -0
- package/dist/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerModal.d.ts +53 -0
- package/dist/loadBalancer/httpLoadBalancerUtils.service.d.ts +3 -1
- package/dist/serverGroup/configure/wizard/GceCloneServerGroupModal.d.ts +3 -0
- package/dist/serverGroup/configure/wizard/GceServerGroupWizard.types.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/customInstance/CustomInstanceConfigurer.d.ts +1 -1
- package/dist/serverGroup/configure/wizard/customInstance/GceCustomInstanceBuilder.d.ts +2 -2
- package/dist/serverGroup/configure/wizard/pages/AdvancedSettings.d.ts +3 -0
- package/dist/serverGroup/configure/wizard/pages/GceServerGroupLoadBalancers.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/pages/Policies.d.ts +21 -3
- package/dist/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/pages/ServerGroupCapacity.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/pages/ServerGroupImageSettings.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/pages/ServerGroupInstanceType.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/zones/GceInstanceFlexibilityConfigurer.d.ts +2 -2
- package/dist/serverGroup/details/GceInstanceFlexibilityPolicyDetails.d.ts +2 -2
- package/dist/serverGroup/details/stateful/MarkDiskStatefulButton.d.ts +2 -2
- package/dist/serverGroup/details/stateful/UpdateBootImageButton.d.ts +2 -2
- package/package.json +5 -5
- package/src/autoHealingPolicy/GceAutoHealingPolicyEditor.spec.tsx +68 -52
- package/src/autoscalingPolicy/GceAutoscalingPolicyEditor.spec.tsx +43 -50
- package/src/autoscalingPolicy/GceAutoscalingPolicyEditor.tsx +1 -1
- package/src/autoscalingPolicy/GceAutoscalingPolicyWriter.spec.ts +1 -1
- package/src/domain/loadBalancer.ts +6 -3
- package/src/gce.module.spec.ts +132 -153
- package/src/instance/details/GceInstanceDetails.spec.tsx +86 -83
- package/src/loadBalancer/configure/choice/GceLoadBalancerChoiceModal.spec.tsx +162 -121
- package/src/loadBalancer/configure/choice/GceLoadBalancerChoiceModal.tsx +13 -1
- package/src/loadBalancer/configure/common/GceProxyLoadBalancerEditor.spec.tsx +47 -30
- package/src/loadBalancer/configure/common/GceProxyLoadBalancerEditor.tsx +2 -1
- package/src/loadBalancer/configure/common/GceProxyLoadBalancerModal.spec.tsx +85 -57
- package/src/loadBalancer/configure/common/gceLoadBalancerData.spec.ts +17 -16
- package/src/loadBalancer/configure/common/gceLoadBalancerModels.spec.ts +230 -5
- package/src/loadBalancer/configure/common/gceLoadBalancerModels.ts +186 -22
- package/src/loadBalancer/configure/common/gceLoadBalancerSubmission.spec.ts +298 -2
- package/src/loadBalancer/configure/common/gceLoadBalancerSubmission.ts +35 -5
- package/src/loadBalancer/configure/common/useGceLoadBalancerData.spec.tsx +37 -46
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerEditor.spec.tsx +399 -58
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerEditor.tsx +148 -41
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerListenerEditor.spec.tsx +169 -36
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerListenerEditor.tsx +80 -12
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerModal.spec.tsx +224 -124
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerModal.tsx +8 -2
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerResourceEditors.spec.tsx +57 -22
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerResourceEditors.tsx +21 -0
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerRoutingEditor.spec.tsx +24 -20
- package/src/loadBalancer/configure/network/GceNetworkLoadBalancerEditor.spec.tsx +41 -32
- package/src/loadBalancer/configure/network/GceNetworkLoadBalancerModal.spec.tsx +35 -30
- package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerEditor.spec.tsx +443 -0
- package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerEditor.tsx +401 -0
- package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerModal.spec.tsx +330 -0
- package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerModal.tsx +348 -0
- package/src/loadBalancer/details/gceLoadBalancerDetails.spec.tsx +439 -35
- package/src/loadBalancer/details/gceLoadBalancerDetails.tsx +104 -21
- package/src/loadBalancer/httpLoadBalancerUtils.service.spec.ts +126 -0
- package/src/loadBalancer/httpLoadBalancerUtils.service.ts +27 -11
- package/src/loadBalancer/loadBalancer.setTransformer.spec.ts +165 -0
- package/src/loadBalancer/loadBalancer.setTransformer.ts +15 -2
- package/src/pipeline/stages/googleStageConfig.spec.js +22 -16
- package/src/securityGroup/configure/GceSecurityGroupModal.spec.tsx +177 -193
- package/src/securityGroup/configure/GceSecurityGroupModal.tsx +14 -2
- package/src/securityGroup/details/GceSecurityGroupDetails.spec.tsx +69 -78
- package/src/serverGroup/configure/serverGroupCommandBuilder.service.spec.js +35 -15
- package/src/serverGroup/configure/serverGroupConfiguration.service.js +7 -1
- package/src/serverGroup/configure/serverGroupConfiguration.service.spec.js +74 -4
- package/src/serverGroup/configure/wizard/GceCloneServerGroupModal.spec.tsx +593 -855
- package/src/serverGroup/configure/wizard/GceCloneServerGroupModal.tsx +120 -38
- package/src/serverGroup/configure/wizard/GceServerGroupWizard.types.ts +1 -0
- package/src/serverGroup/configure/wizard/GceServerGroupWizardPage.spec.tsx +53 -50
- package/src/serverGroup/configure/wizard/customInstance/GceCustomInstanceBuilder.spec.tsx +65 -49
- package/src/serverGroup/configure/wizard/hiddenMetadataKeys.value.js +1 -0
- package/src/serverGroup/configure/wizard/pages/AdvancedSettings.spec.tsx +101 -108
- package/src/serverGroup/configure/wizard/pages/AdvancedSettings.tsx +32 -28
- package/src/serverGroup/configure/wizard/pages/GceServerGroupFirewalls.spec.tsx +60 -59
- package/src/serverGroup/configure/wizard/pages/GceServerGroupLoadBalancers.spec.tsx +240 -114
- package/src/serverGroup/configure/wizard/pages/GceServerGroupLoadBalancers.tsx +152 -63
- package/src/serverGroup/configure/wizard/pages/Policies.spec.tsx +93 -48
- package/src/serverGroup/configure/wizard/pages/Policies.tsx +80 -55
- package/src/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.spec.tsx +123 -94
- package/src/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.tsx +24 -18
- package/src/serverGroup/configure/wizard/pages/ServerGroupCapacity.spec.tsx +152 -133
- package/src/serverGroup/configure/wizard/pages/ServerGroupCapacity.tsx +40 -37
- package/src/serverGroup/configure/wizard/pages/ServerGroupImageSettings.spec.tsx +66 -60
- package/src/serverGroup/configure/wizard/pages/ServerGroupImageSettings.tsx +8 -2
- package/src/serverGroup/configure/wizard/pages/ServerGroupInstanceType.spec.tsx +131 -101
- package/src/serverGroup/configure/wizard/pages/ServerGroupInstanceType.tsx +29 -28
- package/src/serverGroup/configure/wizard/zones/GceInstanceFlexibilityConfigurer.spec.tsx +104 -81
- package/src/serverGroup/details/GceInstanceFlexibilityPolicyDetails.spec.tsx +10 -11
- package/src/serverGroup/details/autoHealingPolicy/GceAutoHealingPolicyDetails.spec.tsx +51 -59
- package/src/serverGroup/details/autoHealingPolicy/modal/GceUpsertAutoHealingPolicyModal.spec.tsx +3 -3
- package/src/serverGroup/details/autoscalingPolicy/GceAutoscalingPolicyDetails.spec.tsx +49 -58
- package/src/serverGroup/details/autoscalingPolicy/modal/GceUpsertAutoscalingPolicyModal.spec.tsx +3 -3
- package/src/serverGroup/details/gceServerGroupDetails.spec.tsx +162 -141
- package/src/serverGroup/details/resize/GceResizeServerGroupModal.spec.tsx +102 -86
- package/src/serverGroup/details/resize/GceResizeServerGroupModal.tsx +4 -0
- package/src/serverGroup/details/rollback/GceRollbackServerGroupModal.spec.tsx +40 -31
- package/src/serverGroup/serverGroup.transformer.js +1 -0
- package/src/serverGroup/serverGroup.transformer.spec.js +48 -0
- package/src/validation/ApplicationNameValidator.spec.ts +1 -1
- /package/dist/{gce.logo2ae85d8704b1d6b8.png → gce.logoChI3w_A5.png} +0 -0
|
@@ -10,6 +10,8 @@ import type {
|
|
|
10
10
|
import { GceServerGroupWizardPage } from '../GceServerGroupWizardPage';
|
|
11
11
|
|
|
12
12
|
interface ILoadBalancerOption {
|
|
13
|
+
loadBalancerType?: string;
|
|
14
|
+
listeners?: Array<{ name?: string }>;
|
|
13
15
|
name: string;
|
|
14
16
|
region?: string;
|
|
15
17
|
}
|
|
@@ -91,53 +93,69 @@ function renderPolicyError(error: string | undefined, id: string): React.ReactNo
|
|
|
91
93
|
);
|
|
92
94
|
}
|
|
93
95
|
|
|
94
|
-
export
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
96
|
+
export function validateGceServerGroupLoadBalancers(
|
|
97
|
+
values: IGceServerGroupCommand,
|
|
98
|
+
): IGceServerGroupCommandValidationErrors {
|
|
99
|
+
const errors = {} as IGceServerGroupCommandValidationErrors & {
|
|
100
|
+
loadBalancingPolicy?: ILoadBalancingPolicyErrors;
|
|
101
|
+
};
|
|
102
|
+
if (hasAmbiguousSelectedName(values)) {
|
|
103
|
+
errors.loadBalancers =
|
|
104
|
+
'The selected load balancer name is ambiguous across regions or load balancer types. Rename it or select an unambiguous load balancer.';
|
|
105
|
+
} else if (hasMixedRegionalExternalNetworkSelection(values)) {
|
|
106
|
+
errors.loadBalancers =
|
|
107
|
+
'REGIONAL_EXTERNAL_NETWORK load balancers cannot be combined with other load balancer families in this editor.';
|
|
108
|
+
}
|
|
109
|
+
const policy = values.loadBalancingPolicy as ILoadBalancingPolicy | undefined;
|
|
110
|
+
if (!policy || !getBalancingModes(values).length) {
|
|
111
|
+
return errors;
|
|
112
|
+
}
|
|
103
113
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
114
|
+
const policyErrors: ILoadBalancingPolicyErrors = {};
|
|
115
|
+
const balancingModes = getBalancingModes(values);
|
|
116
|
+
if (!policy.balancingMode || !balancingModes.includes(policy.balancingMode)) {
|
|
117
|
+
policyErrors.balancingMode = 'Select a balancing mode supported by the selected load balancers.';
|
|
118
|
+
}
|
|
119
|
+
if (!isValidBoundedValue(policy.capacityScaler, values, 0, 1)) {
|
|
120
|
+
policyErrors.capacityScaler = 'Capacity must be between 0 and 100%.';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const namedPortErrors = (policy.namedPorts || []).map(({ name, port }) => {
|
|
124
|
+
const namedPortError: { name?: string; port?: string } = {};
|
|
125
|
+
if (!name?.trim()) {
|
|
126
|
+
namedPortError.name = 'Port name required.';
|
|
108
127
|
}
|
|
109
|
-
if (!
|
|
110
|
-
|
|
128
|
+
if (!isValidInteger(port, values, 1, 65535)) {
|
|
129
|
+
namedPortError.port = 'Port must be an integer between 1 and 65535.';
|
|
111
130
|
}
|
|
131
|
+
return namedPortError;
|
|
132
|
+
});
|
|
133
|
+
if (namedPortErrors.some((namedPortError) => Object.keys(namedPortError).length)) {
|
|
134
|
+
policyErrors.namedPorts = namedPortErrors;
|
|
135
|
+
}
|
|
112
136
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
});
|
|
123
|
-
if (namedPortErrors.some((namedPortError) => Object.keys(namedPortError).length)) {
|
|
124
|
-
policyErrors.namedPorts = namedPortErrors;
|
|
125
|
-
}
|
|
137
|
+
if (policy.balancingMode === 'RATE' && !isValidMinimum(policy.maxRatePerInstance, values, 0)) {
|
|
138
|
+
policyErrors.maxRatePerInstance = 'Max rate must be a finite number greater than or equal to zero.';
|
|
139
|
+
}
|
|
140
|
+
if (policy.balancingMode === 'CONNECTION' && !isValidMinimum(policy.maxConnectionsPerInstance, values, 0)) {
|
|
141
|
+
policyErrors.maxConnectionsPerInstance = 'Max connections must be a finite number greater than or equal to zero.';
|
|
142
|
+
}
|
|
143
|
+
if (policy.balancingMode === 'UTILIZATION' && !isValidBoundedValue(policy.maxUtilization, values, 0, 1)) {
|
|
144
|
+
policyErrors.maxUtilization = 'Max utilization must be between 0 and 100%.';
|
|
145
|
+
}
|
|
126
146
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
if (policy.balancingMode === 'UTILIZATION' && !isValidBoundedValue(policy.maxUtilization, values, 0, 1)) {
|
|
134
|
-
policyErrors.maxUtilization = 'Max utilization must be between 0 and 100%.';
|
|
135
|
-
}
|
|
147
|
+
if (Object.keys(policyErrors).length) {
|
|
148
|
+
errors.loadBalancingPolicy = policyErrors;
|
|
149
|
+
}
|
|
150
|
+
return errors;
|
|
151
|
+
}
|
|
136
152
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
153
|
+
export class GceServerGroupLoadBalancers extends GceServerGroupWizardPage<IGceServerGroupWizardPageProps> {
|
|
154
|
+
public validate(values: IGceServerGroupCommand): IGceServerGroupCommandValidationErrors {
|
|
155
|
+
return {
|
|
156
|
+
...super.validate(values),
|
|
157
|
+
...validateGceServerGroupLoadBalancers(values),
|
|
158
|
+
} as IGceServerGroupCommandValidationErrors;
|
|
141
159
|
}
|
|
142
160
|
|
|
143
161
|
public render(): React.ReactElement {
|
|
@@ -145,9 +163,10 @@ export class GceServerGroupLoadBalancers extends GceServerGroupWizardPage<IGceSe
|
|
|
145
163
|
const selectedLoadBalancers = uniqueStrings(values.loadBalancers);
|
|
146
164
|
const selectedReferences = preserveReferences(getAvailableLoadBalancers(values), selectedLoadBalancers);
|
|
147
165
|
const policy = (values.loadBalancingPolicy || {}) as ILoadBalancingPolicy;
|
|
148
|
-
const
|
|
166
|
+
const validationErrors = this.validate(values) as IGceServerGroupCommandValidationErrors & {
|
|
149
167
|
loadBalancingPolicy?: ILoadBalancingPolicyErrors;
|
|
150
|
-
}
|
|
168
|
+
};
|
|
169
|
+
const policyErrors = validationErrors.loadBalancingPolicy || { namedPorts: [] };
|
|
151
170
|
const balancingModes = getBalancingModes(values);
|
|
152
171
|
|
|
153
172
|
return (
|
|
@@ -172,6 +191,11 @@ export class GceServerGroupLoadBalancers extends GceServerGroupWizardPage<IGceSe
|
|
|
172
191
|
</option>
|
|
173
192
|
))}
|
|
174
193
|
</select>
|
|
194
|
+
{validationErrors.loadBalancers && (
|
|
195
|
+
<span className="help-block" id="gce-server-group-load-balancers-error" role="alert">
|
|
196
|
+
{validationErrors.loadBalancers}
|
|
197
|
+
</span>
|
|
198
|
+
)}
|
|
175
199
|
</div>
|
|
176
200
|
<div className="col-md-1">
|
|
177
201
|
<button
|
|
@@ -257,6 +281,11 @@ export class GceServerGroupLoadBalancers extends GceServerGroupWizardPage<IGceSe
|
|
|
257
281
|
values: IGceServerGroupCommand,
|
|
258
282
|
loadBalancerName: string,
|
|
259
283
|
): React.ReactElement | null {
|
|
284
|
+
// Clouddriver attaches every backend service of a regional external load balancer.
|
|
285
|
+
const loadBalancerType = getLoadBalancerIndex(values)[loadBalancerName]?.loadBalancerType;
|
|
286
|
+
if (loadBalancerType === 'REGIONAL_EXTERNAL_NETWORK' || loadBalancerType === 'EXTERNAL_MANAGED') {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
260
289
|
const availableBackendServices = getBackendServiceData(values, loadBalancerName).map(({ name }) => name);
|
|
261
290
|
const selectedBackendServices = uniqueStrings(values.backendServices?.[loadBalancerName]);
|
|
262
291
|
if (!availableBackendServices.length && !selectedBackendServices.length) {
|
|
@@ -578,17 +607,10 @@ export class GceServerGroupLoadBalancers extends GceServerGroupWizardPage<IGceSe
|
|
|
578
607
|
}
|
|
579
608
|
|
|
580
609
|
function getAvailableLoadBalancers(command: IGceServerGroupCommand): ILoadBalancerOption[] {
|
|
581
|
-
const
|
|
582
|
-
if (
|
|
610
|
+
const scopedLoadBalancers = getScopedLoadBalancers(command);
|
|
611
|
+
if (scopedLoadBalancers) {
|
|
583
612
|
return uniqueLoadBalancers(
|
|
584
|
-
|
|
585
|
-
.flatMap((provider: any) => (Array.isArray(provider?.accounts) ? provider.accounts : []))
|
|
586
|
-
.filter((account: any) => account?.name === command.credentials)
|
|
587
|
-
.flatMap((account: any) => (Array.isArray(account?.regions) ? account.regions : []))
|
|
588
|
-
.flatMap((region: any) => (Array.isArray(region?.loadBalancers) ? region.loadBalancers : []))
|
|
589
|
-
.filter((loadBalancer: any) => isLoadBalancerInRegion(loadBalancer, command))
|
|
590
|
-
.map(toLoadBalancerOption)
|
|
591
|
-
.filter((loadBalancer): loadBalancer is ILoadBalancerOption => Boolean(loadBalancer)),
|
|
613
|
+
scopedLoadBalancers.map((loadBalancer) => toIndexedExternalManagedOption(command, loadBalancer)),
|
|
592
614
|
);
|
|
593
615
|
}
|
|
594
616
|
|
|
@@ -603,6 +625,23 @@ function getAvailableLoadBalancers(command: IGceServerGroupCommand): ILoadBalanc
|
|
|
603
625
|
);
|
|
604
626
|
}
|
|
605
627
|
|
|
628
|
+
// Clouddriver summarizes EXTERNAL_MANAGED per listener, while the wizard indexes it by the URL-map
|
|
629
|
+
// display name that clone submission expands back into listener names.
|
|
630
|
+
function toIndexedExternalManagedOption(
|
|
631
|
+
command: IGceServerGroupCommand,
|
|
632
|
+
loadBalancer: ILoadBalancerOption,
|
|
633
|
+
): ILoadBalancerOption {
|
|
634
|
+
if (loadBalancer.loadBalancerType !== 'EXTERNAL_MANAGED') {
|
|
635
|
+
return loadBalancer;
|
|
636
|
+
}
|
|
637
|
+
const indexed = Object.values(getLoadBalancerIndex(command)).find(
|
|
638
|
+
(candidate) =>
|
|
639
|
+
candidate.loadBalancerType === 'EXTERNAL_MANAGED' &&
|
|
640
|
+
(candidate.listeners || []).some(({ name }) => name === loadBalancer.name),
|
|
641
|
+
);
|
|
642
|
+
return indexed?.name ? { ...loadBalancer, name: indexed.name } : loadBalancer;
|
|
643
|
+
}
|
|
644
|
+
|
|
606
645
|
function getLoadBalancerIndex(command: IGceServerGroupCommand): Record<string, ILoadBalancerData> {
|
|
607
646
|
return (command.backingData?.filtered?.loadBalancerIndex || {}) as Record<string, ILoadBalancerData>;
|
|
608
647
|
}
|
|
@@ -635,14 +674,19 @@ function getNamedPortNames(command: IGceServerGroupCommand): string[] {
|
|
|
635
674
|
|
|
636
675
|
function getBalancingModes(command: IGceServerGroupCommand): string[] {
|
|
637
676
|
const loadBalancerIndex = getLoadBalancerIndex(command);
|
|
638
|
-
const
|
|
677
|
+
const loadBalancerTypes = uniqueStrings(command.loadBalancers)
|
|
639
678
|
.map((loadBalancerName) => loadBalancerIndex[loadBalancerName]?.loadBalancerType)
|
|
640
|
-
.filter((loadBalancerType): loadBalancerType is string => Boolean(loadBalancerType))
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
679
|
+
.filter((loadBalancerType): loadBalancerType is string => Boolean(loadBalancerType));
|
|
680
|
+
// Clouddriver always attaches passthrough NLB backends with CONNECTION balancing and this page
|
|
681
|
+
// rejects mixing them with other families, so there is no balancing mode to choose.
|
|
682
|
+
if (loadBalancerTypes.includes('REGIONAL_EXTERNAL_NETWORK')) {
|
|
683
|
+
return [];
|
|
684
|
+
}
|
|
685
|
+
const modeSets = loadBalancerTypes.map((loadBalancerType) =>
|
|
686
|
+
loadBalancerType === 'HTTP' || loadBalancerType === 'INTERNAL_MANAGED' || loadBalancerType === 'EXTERNAL_MANAGED'
|
|
687
|
+
? HTTP_BALANCING_MODES
|
|
688
|
+
: CONNECTION_BALANCING_MODES,
|
|
689
|
+
);
|
|
646
690
|
if (!modeSets.length) {
|
|
647
691
|
const persistedMode = (command.loadBalancingPolicy as ILoadBalancingPolicy | undefined)?.balancingMode;
|
|
648
692
|
return persistedMode ? [persistedMode] : [];
|
|
@@ -706,7 +750,11 @@ function getLoadBalancerMetadataReference(
|
|
|
706
750
|
if (!loadBalancer) {
|
|
707
751
|
return undefined;
|
|
708
752
|
}
|
|
709
|
-
if (
|
|
753
|
+
if (
|
|
754
|
+
loadBalancer.loadBalancerType === 'HTTP' ||
|
|
755
|
+
loadBalancer.loadBalancerType === 'INTERNAL_MANAGED' ||
|
|
756
|
+
loadBalancer.loadBalancerType === 'EXTERNAL_MANAGED'
|
|
757
|
+
) {
|
|
710
758
|
const names = uniqueStrings((loadBalancer.listeners || []).map(({ name }) => name || ''));
|
|
711
759
|
if (!names.length) {
|
|
712
760
|
return undefined;
|
|
@@ -828,13 +876,54 @@ function isLoadBalancerInRegion(loadBalancer: any, command: IGceServerGroupComma
|
|
|
828
876
|
return loadBalancer?.region === command.region || loadBalancer?.region === 'global';
|
|
829
877
|
}
|
|
830
878
|
|
|
879
|
+
function getScopedLoadBalancers(command: IGceServerGroupCommand): ILoadBalancerOption[] | undefined {
|
|
880
|
+
const rawLoadBalancers = command.backingData?.loadBalancers;
|
|
881
|
+
if (!Array.isArray(rawLoadBalancers)) {
|
|
882
|
+
return undefined;
|
|
883
|
+
}
|
|
884
|
+
return rawLoadBalancers
|
|
885
|
+
.flatMap((provider: any) => (Array.isArray(provider?.accounts) ? provider.accounts : []))
|
|
886
|
+
.filter((account: any) => account?.name === command.credentials)
|
|
887
|
+
.flatMap((account: any) => (Array.isArray(account?.regions) ? account.regions : []))
|
|
888
|
+
.flatMap((region: any) => (Array.isArray(region?.loadBalancers) ? region.loadBalancers : []))
|
|
889
|
+
.filter((loadBalancer: any) => isLoadBalancerInRegion(loadBalancer, command))
|
|
890
|
+
.map(toLoadBalancerOption)
|
|
891
|
+
.filter((loadBalancer): loadBalancer is ILoadBalancerOption => Boolean(loadBalancer));
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function hasAmbiguousSelectedName(command: IGceServerGroupCommand): boolean {
|
|
895
|
+
const selectedNames = new Set(uniqueStrings(command.loadBalancers));
|
|
896
|
+
const identitiesByName = (getScopedLoadBalancers(command) || []).reduce<Map<string, Set<string>>>(
|
|
897
|
+
(identities, loadBalancer) => {
|
|
898
|
+
const identity = `${loadBalancer.region || ''}:${loadBalancer.loadBalancerType || ''}`;
|
|
899
|
+
[loadBalancer.name, ...(loadBalancer.listeners || []).map(({ name }) => name)]
|
|
900
|
+
.filter((name): name is string => Boolean(name))
|
|
901
|
+
.forEach((name) => {
|
|
902
|
+
const values = identities.get(name) || new Set<string>();
|
|
903
|
+
values.add(identity);
|
|
904
|
+
identities.set(name, values);
|
|
905
|
+
});
|
|
906
|
+
return identities;
|
|
907
|
+
},
|
|
908
|
+
new Map(),
|
|
909
|
+
);
|
|
910
|
+
return Array.from(selectedNames).some((name) => (identitiesByName.get(name)?.size || 0) > 1);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
function hasMixedRegionalExternalNetworkSelection(command: IGceServerGroupCommand): boolean {
|
|
914
|
+
const selectedTypes = uniqueStrings(command.loadBalancers)
|
|
915
|
+
.map((name) => getLoadBalancerIndex(command)[name]?.loadBalancerType)
|
|
916
|
+
.filter(Boolean);
|
|
917
|
+
return selectedTypes.includes('REGIONAL_EXTERNAL_NETWORK') && selectedTypes.length > 1;
|
|
918
|
+
}
|
|
919
|
+
|
|
831
920
|
function toLoadBalancerOption(loadBalancer: unknown): ILoadBalancerOption | null {
|
|
832
921
|
if (typeof loadBalancer === 'string') {
|
|
833
922
|
return { name: loadBalancer };
|
|
834
923
|
}
|
|
835
924
|
if (loadBalancer && typeof (loadBalancer as ILoadBalancerOption).name === 'string') {
|
|
836
|
-
const { name, region } = loadBalancer as ILoadBalancerOption;
|
|
837
|
-
return { name, region };
|
|
925
|
+
const { listeners, loadBalancerType, name, region } = loadBalancer as ILoadBalancerOption;
|
|
926
|
+
return { listeners, loadBalancerType, name, region };
|
|
838
927
|
}
|
|
839
928
|
return null;
|
|
840
929
|
}
|
|
@@ -1,10 +1,48 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
|
|
4
4
|
import type { IGceServerGroupCommand } from '../GceServerGroupWizard.types';
|
|
5
|
-
import { Policies } from './Policies';
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import { Policies, validateGceServerGroupPolicies } from './Policies';
|
|
6
|
+
|
|
7
|
+
vi.mock('../../../../autoscalingPolicy', async (importOriginal) => {
|
|
8
|
+
const actual = await importOriginal<typeof import('../../../../autoscalingPolicy')>();
|
|
9
|
+
return {
|
|
10
|
+
...actual,
|
|
11
|
+
GceAutoscalingPolicyEditor: ({ onChange, policy }: any) => (
|
|
12
|
+
<div>
|
|
13
|
+
<output aria-label="Autoscaling policy">{JSON.stringify(policy)}</output>
|
|
14
|
+
<button
|
|
15
|
+
onClick={() =>
|
|
16
|
+
onChange({
|
|
17
|
+
minNumReplicas: 2,
|
|
18
|
+
maxNumReplicas: 6,
|
|
19
|
+
coolDownPeriodSec: 90,
|
|
20
|
+
cpuUtilization: { utilizationTarget: 0.7 },
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
type="button"
|
|
24
|
+
>
|
|
25
|
+
Edit autoscaling policy
|
|
26
|
+
</button>
|
|
27
|
+
</div>
|
|
28
|
+
),
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
vi.mock('../../../../autoHealingPolicy', () => ({
|
|
33
|
+
GceAutoHealingPolicyEditor: ({ policy, reader }: any) => {
|
|
34
|
+
const [healthChecks, setHealthChecks] = React.useState<any[]>([]);
|
|
35
|
+
return (
|
|
36
|
+
<div>
|
|
37
|
+
<output aria-label="Autohealing policy">{JSON.stringify(policy)}</output>
|
|
38
|
+
<button onClick={() => reader.listHealthChecks().then(setHealthChecks)} type="button">
|
|
39
|
+
Load health checks
|
|
40
|
+
</button>
|
|
41
|
+
<output aria-label="Health checks">{JSON.stringify(healthChecks)}</output>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
},
|
|
45
|
+
}));
|
|
8
46
|
|
|
9
47
|
describe('GCE server group Policies page', () => {
|
|
10
48
|
it('reuses the policy editors and supplies autohealing health checks from filtered backing data', async () => {
|
|
@@ -27,14 +65,16 @@ describe('GCE server group Policies page', () => {
|
|
|
27
65
|
},
|
|
28
66
|
},
|
|
29
67
|
});
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
expect(
|
|
33
|
-
expect(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
68
|
+
renderPolicies(values);
|
|
69
|
+
|
|
70
|
+
expect(screen.getByLabelText('Autoscaling policy')).toHaveTextContent('unknownPolicyField');
|
|
71
|
+
expect(screen.getByLabelText('Autohealing policy')).toHaveTextContent('unknownPolicyField');
|
|
72
|
+
fireEvent.click(screen.getByRole('button', { name: 'Load health checks' }));
|
|
73
|
+
await waitFor(() =>
|
|
74
|
+
expect(screen.getByLabelText('Health checks')).toHaveTextContent('https://compute/healthChecks/check'),
|
|
75
|
+
);
|
|
76
|
+
expect(JSON.parse(screen.getByLabelText('Health checks').textContent || '[]')).toEqual([
|
|
77
|
+
expect.objectContaining({ account: 'account', name: 'check', selfLink: 'https://compute/healthChecks/check' }),
|
|
38
78
|
]);
|
|
39
79
|
});
|
|
40
80
|
|
|
@@ -47,10 +87,9 @@ describe('GCE server group Policies page', () => {
|
|
|
47
87
|
source: { region: 'us-central1', serverGroupName: 'app-v001', useSourceCapacity: true },
|
|
48
88
|
viewState: { mode: 'clone', useSimpleCapacity: false, unrelated: 'keep' },
|
|
49
89
|
});
|
|
50
|
-
const
|
|
51
|
-
const persistedPage = shallow(<Policies app={{} as any} formik={persistedFormik} />);
|
|
90
|
+
const page = renderPolicies(persisted);
|
|
52
91
|
|
|
53
|
-
|
|
92
|
+
fireEvent.click(screen.getByTestId('enable-autoscaling'));
|
|
54
93
|
|
|
55
94
|
expect(persisted.enableAutoScaling).toBe(false);
|
|
56
95
|
expect(persisted.autoscalingPolicy).toBeNull();
|
|
@@ -63,8 +102,8 @@ describe('GCE server group Policies page', () => {
|
|
|
63
102
|
expect(persisted.viewState).toEqual({ mode: 'clone', useSimpleCapacity: true, unrelated: 'keep' });
|
|
64
103
|
expect(persisted.capacity).toEqual({ min: 2, max: 2, desired: 2 });
|
|
65
104
|
|
|
66
|
-
|
|
67
|
-
|
|
105
|
+
page.rerenderPage(persisted);
|
|
106
|
+
fireEvent.click(screen.getByTestId('enable-autoscaling'));
|
|
68
107
|
|
|
69
108
|
expect(persisted.enableAutoScaling).toBe(true);
|
|
70
109
|
expect(persisted.overwriteAncestorAutoscalingPolicy).toBe(false);
|
|
@@ -85,9 +124,9 @@ describe('GCE server group Policies page', () => {
|
|
|
85
124
|
autoscalingPolicy: { minNumReplicas: 1, maxNumReplicas: 4 },
|
|
86
125
|
viewState: { mode },
|
|
87
126
|
});
|
|
88
|
-
|
|
127
|
+
renderPolicies(values);
|
|
89
128
|
|
|
90
|
-
|
|
129
|
+
fireEvent.click(screen.getByTestId('enable-autoscaling'));
|
|
91
130
|
|
|
92
131
|
expect(values.overwriteAncestorAutoscalingPolicy).toBe(false);
|
|
93
132
|
});
|
|
@@ -96,17 +135,18 @@ describe('GCE server group Policies page', () => {
|
|
|
96
135
|
it('preserves autohealing policy fields and limits ancestor overwrite to clone disables', () => {
|
|
97
136
|
const policy = { healthCheck: 'check', initialDelaySec: 300, unknownPolicyField: 'keep' };
|
|
98
137
|
const clone = command({ enableAutoHealing: true, autoHealingPolicy: policy, viewState: { mode: 'clone' } });
|
|
99
|
-
const
|
|
138
|
+
const page = renderPolicies(clone);
|
|
100
139
|
|
|
101
|
-
|
|
140
|
+
fireEvent.click(screen.getByTestId('enable-autohealing'));
|
|
102
141
|
|
|
103
142
|
expect(clone.enableAutoHealing).toBe(false);
|
|
104
143
|
expect(clone.overwriteAncestorAutoHealingPolicy).toBe(true);
|
|
105
144
|
expect(clone.autoHealingPolicy).toBe(policy);
|
|
106
145
|
|
|
107
146
|
const create = command({ enableAutoHealing: true, autoHealingPolicy: policy, viewState: { mode: 'create' } });
|
|
108
|
-
|
|
109
|
-
|
|
147
|
+
page.unmount();
|
|
148
|
+
renderPolicies(create);
|
|
149
|
+
fireEvent.click(screen.getByTestId('enable-autohealing'));
|
|
110
150
|
|
|
111
151
|
expect(create.overwriteAncestorAutoHealingPolicy).toBe(false);
|
|
112
152
|
});
|
|
@@ -122,10 +162,11 @@ describe('GCE server group Policies page', () => {
|
|
|
122
162
|
source: { useSourceCapacity: true },
|
|
123
163
|
viewState: { mode: 'clone' },
|
|
124
164
|
});
|
|
125
|
-
const
|
|
165
|
+
const page = renderPolicies(values);
|
|
126
166
|
|
|
127
|
-
|
|
128
|
-
|
|
167
|
+
fireEvent.click(screen.getByTestId('enable-autoscaling'));
|
|
168
|
+
page.rerenderPage(values);
|
|
169
|
+
fireEvent.click(screen.getByTestId('enable-autohealing'));
|
|
129
170
|
|
|
130
171
|
expect(values.autoscalingPolicy).toEqual({
|
|
131
172
|
minNumReplicas: 3,
|
|
@@ -141,18 +182,17 @@ describe('GCE server group Policies page', () => {
|
|
|
141
182
|
});
|
|
142
183
|
|
|
143
184
|
it('derives autoscaling rendering and validation from canonical policy presence', () => {
|
|
144
|
-
const page = new Policies({ app: {} as any, formik: formik(command()) } as any);
|
|
145
185
|
const canonical = command({
|
|
146
186
|
enableAutoScaling: false,
|
|
147
187
|
autoscalingPolicy: { minNumReplicas: 3, maxNumReplicas: 2, cpuUtilization: {} },
|
|
148
188
|
});
|
|
149
|
-
|
|
189
|
+
renderPolicies(canonical);
|
|
150
190
|
|
|
151
|
-
expect(
|
|
152
|
-
expect(
|
|
191
|
+
expect(screen.getByTestId('enable-autoscaling')).toBeChecked();
|
|
192
|
+
expect(screen.getByLabelText('Autoscaling policy')).toBeInTheDocument();
|
|
153
193
|
|
|
154
194
|
expect(
|
|
155
|
-
|
|
195
|
+
validateGceServerGroupPolicies({
|
|
156
196
|
...canonical,
|
|
157
197
|
enableAutoHealing: true,
|
|
158
198
|
autoHealingPolicy: {
|
|
@@ -174,7 +214,7 @@ describe('GCE server group Policies page', () => {
|
|
|
174
214
|
},
|
|
175
215
|
});
|
|
176
216
|
expect(
|
|
177
|
-
|
|
217
|
+
validateGceServerGroupPolicies(
|
|
178
218
|
command({
|
|
179
219
|
enableAutoScaling: true,
|
|
180
220
|
autoscalingPolicy: null,
|
|
@@ -198,17 +238,16 @@ describe('GCE server group Policies page', () => {
|
|
|
198
238
|
source: { useSourceCapacity: true },
|
|
199
239
|
viewState: { mode: 'editPipeline', useSimpleCapacity: true, unrelated: 'keep' },
|
|
200
240
|
});
|
|
201
|
-
|
|
202
|
-
|
|
241
|
+
renderPolicies(values);
|
|
242
|
+
|
|
243
|
+
fireEvent.click(screen.getByRole('button', { name: 'Edit autoscaling policy' }));
|
|
244
|
+
|
|
245
|
+
expect(values.autoscalingPolicy).toEqual({
|
|
203
246
|
minNumReplicas: 2,
|
|
204
247
|
maxNumReplicas: 6,
|
|
205
248
|
coolDownPeriodSec: 90,
|
|
206
249
|
cpuUtilization: { utilizationTarget: 0.7 },
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
wrapper.find(GceAutoscalingPolicyEditor).prop('onChange')(policy);
|
|
210
|
-
|
|
211
|
-
expect(values.autoscalingPolicy).toBe(policy);
|
|
250
|
+
});
|
|
212
251
|
expect(values.capacity).toEqual({ min: 2, max: 6, desired: 3 });
|
|
213
252
|
expect(values.enableAutoScaling).toBe(true);
|
|
214
253
|
expect(values.source.useSourceCapacity).toBe(false);
|
|
@@ -216,10 +255,8 @@ describe('GCE server group Policies page', () => {
|
|
|
216
255
|
});
|
|
217
256
|
|
|
218
257
|
it('requires autoscaling capacity, cooldown, and real metric values', () => {
|
|
219
|
-
const page = new Policies({ app: {} as any, formik: formik(command()) } as any);
|
|
220
|
-
|
|
221
258
|
expect(
|
|
222
|
-
|
|
259
|
+
validateGceServerGroupPolicies(
|
|
223
260
|
command({
|
|
224
261
|
enableAutoScaling: true,
|
|
225
262
|
autoscalingPolicy: {
|
|
@@ -240,7 +277,6 @@ describe('GCE server group Policies page', () => {
|
|
|
240
277
|
});
|
|
241
278
|
|
|
242
279
|
it('matches autoscaling schedule and scale-in validation from the completed modal', () => {
|
|
243
|
-
const page = new Policies({ app: {} as any, formik: formik(command()) } as any);
|
|
244
280
|
const values = command({
|
|
245
281
|
enableAutoScaling: true,
|
|
246
282
|
autoscalingPolicy: {
|
|
@@ -253,7 +289,7 @@ describe('GCE server group Policies page', () => {
|
|
|
253
289
|
},
|
|
254
290
|
});
|
|
255
291
|
|
|
256
|
-
expect(
|
|
292
|
+
expect(validateGceServerGroupPolicies(values)).toEqual({
|
|
257
293
|
autoscalingPolicy: {
|
|
258
294
|
scalingSchedules: 'Every scaling schedule must be complete and within supported bounds.',
|
|
259
295
|
scaleInControl: 'Scale-in control values are outside supported bounds.',
|
|
@@ -262,10 +298,8 @@ describe('GCE server group Policies page', () => {
|
|
|
262
298
|
});
|
|
263
299
|
|
|
264
300
|
it('accepts complete policies at modal boundary values', () => {
|
|
265
|
-
const page = new Policies({ app: {} as any, formik: formik(command()) } as any);
|
|
266
|
-
|
|
267
301
|
expect(
|
|
268
|
-
|
|
302
|
+
validateGceServerGroupPolicies(
|
|
269
303
|
command({
|
|
270
304
|
enableAutoScaling: true,
|
|
271
305
|
autoscalingPolicy: {
|
|
@@ -297,6 +331,17 @@ describe('GCE server group Policies page', () => {
|
|
|
297
331
|
});
|
|
298
332
|
});
|
|
299
333
|
|
|
334
|
+
function renderPolicies(values: IGceServerGroupCommand) {
|
|
335
|
+
const pageProps = { app: {} as any, formik: formik(values) };
|
|
336
|
+
const rendered = render(<Policies {...pageProps} />);
|
|
337
|
+
return {
|
|
338
|
+
...rendered,
|
|
339
|
+
rerenderPage(nextValues: IGceServerGroupCommand) {
|
|
340
|
+
rendered.rerender(<Policies {...pageProps} formik={formik(nextValues)} />);
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
300
345
|
function command(overrides: Partial<IGceServerGroupCommand> = {}): IGceServerGroupCommand {
|
|
301
346
|
return {
|
|
302
347
|
credentials: 'account',
|
|
@@ -310,7 +355,7 @@ function command(overrides: Partial<IGceServerGroupCommand> = {}): IGceServerGro
|
|
|
310
355
|
function formik(values: IGceServerGroupCommand): any {
|
|
311
356
|
return {
|
|
312
357
|
values,
|
|
313
|
-
setFieldValue:
|
|
358
|
+
setFieldValue: vi.fn().mockImplementation((field: string, value: any) => {
|
|
314
359
|
values[field] = value;
|
|
315
360
|
}),
|
|
316
361
|
};
|