Merge pull request #11578 from torchiaf/11360-azure-limit-k8s-resources

Changes for Azure cloud provider for RKE2
This commit is contained in:
Francesco Torchia 2024-08-07 18:39:01 +02:00 committed by GitHub
commit 270a1db31f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 375 additions and 19 deletions

View File

@ -1782,7 +1782,9 @@ cluster:
rke2-k3-reprovisioning: 'Making changes to cluster configuration may result in nodes reprovisioning. For more information see the <a href="{docsBase}/how-to-guides/new-user-guides/launch-kubernetes-with-rancher/rke1-vs-rke2-differences#cluster-api" target="_blank" rel="noopener nofollow">documentation</a>.'
desiredNodeGroupWarning: There are 0 nodes available to run the cluster agent. The cluster will not become active until at least one node is available.
haveArgInfo: Configuration information is not available for the selected Kubernetes version. The options available on this screen will be limited; you may want to use the YAML editor.
cloudProviderAddConfig: 'On Kubernetes 1.27 or greater, the Amazon Cloud Provider requires additional configuration. See <a href="https://ranchermanager.docs.rancher.com/how-to-guides/new-user-guides/kubernetes-clusters-in-rancher-setup/set-up-cloud-providers/amazon" target="_blank" rel="noopener noreferrer nofollow">the documentation</a> for more information.'
cloudProviderAddConfig: 'On Kubernetes 1.27 or greater, the <b>Amazon</b> Cloud Provider requires additional configuration. See <a href="https://ranchermanager.docs.rancher.com/how-to-guides/new-user-guides/kubernetes-clusters-in-rancher-setup/set-up-cloud-providers/amazon" target="_blank" rel="noopener noreferrer nofollow">the documentation</a> for more information.'
cloudProviderUnsupportedAzure: 'On Kubernetes 1.30 or greater, the <b>Azure</b> Cloud Provider has been removed. See <a href="https://ranchermanager.docs.rancher.com/how-to-guides/new-user-guides/kubernetes-clusters-in-rancher-setup/set-up-cloud-providers/azure" target="_blank" rel="noopener noreferrer nofollow">the documentation</a> for more information.'
cloudProviderMigrateAzure: 'On Kubernetes 1.30 or greater, the <b>Azure</b> Cloud Provider has been removed. To upgrade Kubernetes, the cluster should be migrated to External provider first.<br> See <a href="https://ranchermanager.docs.rancher.com/how-to-guides/new-user-guides/kubernetes-clusters-in-rancher-setup/set-up-cloud-providers/azure" target="_blank" rel="noopener noreferrer nofollow">the documentation</a> for more information.'
machinePoolError: |-
{count, plural,
=1 { {pool_name}: The provided value for {fields} was not found in the list of expected values. This can happen with clusters provisioned outside of Rancher or when options for the provider have changed. }

View File

@ -27,19 +27,36 @@ const defaultComputed = {
const mockAgentArgs = { 'cloud-provider-name': { options: [], profile: { options: [{ anything: 'yes' }] } } };
const mockServerArgs = { disable: {}, cni: { options: [] } };
const rke2Versions =
[
{
id: 'v1.25.0+rke2r1', value: 'v1.25.0+rke2r1', serverArgs: mockServerArgs, agentArgs: mockAgentArgs, charts: {}
},
{
id: 'v1.24.0+rke2r1', value: 'v1.24.0+rke2r1', serverArgs: mockServerArgs, agentArgs: mockAgentArgs, charts: {}
},
{
id: 'v1.23.0+rke2r1', value: 'v1.23.0+rke2r1', serverArgs: mockServerArgs, agentArgs: mockAgentArgs, charts: {}
}
];
const rke2Versions = [
{
id: 'v1.31.0+rke2r1', value: 'v1.31.0+rke2r1', serverArgs: mockServerArgs, agentArgs: mockAgentArgs, charts: {}
},
{
id: 'v1.30.0+rke2r1', value: 'v1.30.0+rke2r1', serverArgs: mockServerArgs, agentArgs: mockAgentArgs, charts: {}
},
{
id: 'v1.29.1+rke2r1', value: 'v1.29.1+rke2r1', serverArgs: mockServerArgs, agentArgs: mockAgentArgs, charts: {}
},
{
id: 'v1.25.0+rke2r1', value: 'v1.25.0+rke2r1', serverArgs: mockServerArgs, agentArgs: mockAgentArgs, charts: {}
},
{
id: 'v1.24.0+rke2r1', value: 'v1.24.0+rke2r1', serverArgs: mockServerArgs, agentArgs: mockAgentArgs, charts: {}
},
{
id: 'v1.23.0+rke2r1', value: 'v1.23.0+rke2r1', serverArgs: mockServerArgs, agentArgs: mockAgentArgs, charts: {}
}
];
const k3sVersions = [
{
id: 'v1.31.0+k3s1', value: 'v1.31.0+k3s1', serverArgs: mockServerArgs, agentArgs: mockAgentArgs, charts: {}
},
{
id: 'v1.30.0+k3s1', value: 'v1.30.0+k3s1', serverArgs: mockServerArgs, agentArgs: mockAgentArgs, charts: {}
},
{
id: 'v1.29.1+k3s1', value: 'v1.29.1+k3s1', serverArgs: mockServerArgs, agentArgs: mockAgentArgs, charts: {}
},
{
id: 'v1.25.0+k3s1', value: 'v1.25.0+k3s1', serverArgs: mockServerArgs, agentArgs: mockAgentArgs, charts: {}
},
@ -88,7 +105,7 @@ const newOffValue = { ipv6: { enabled: false } };
const bmOnValue = { bandwidthManager: { enabled: true } };
const bmOffValue = { bandwidthManager: { enabled: false } };
function createBasicsTab(version : string, userChartValues: any) {
function createBasicsTab(version : string, userChartValues: any, options = {}) {
const k8s = mockVersionOptions.find((v) => v.id === version) || mockVersionOptions[0];
const label = 'whatever';
const wrapper = mount(Basics, {
@ -122,6 +139,7 @@ function createBasicsTab(version : string, userChartValues: any) {
showCloudProvider: false,
unsupportedCloudProvider: false,
cloudProviderOptions: [{ label: 'Default - RKE2 Embedded', value: '' }],
...options
},
computed: defaultComputed,
mocks: {
@ -470,4 +488,59 @@ describe('component: Basics', () => {
expect(JSON.stringify(latest)).toStrictEqual(expected);
});
it.each([
['create', true, true, '%cluster.banner.cloudProviderUnsupportedAzure%'],
['create', false, true, undefined],
['create', true, false, undefined],
['edit', true, true, undefined],
['view', true, true, undefined],
])('should display Unsupported Azure provider warning message', (mode, showCloudProvider, isAzureProviderUnsupported, warningMessage) => {
const wrapper = createBasicsTab('v1.31.0+rke2r1', {}, {
mode,
showCloudProvider,
isAzureProviderUnsupported,
canAzureMigrateOnEdit: true
});
const cloudProviderUnsupportedAzureWarningMessage = wrapper.find('[data-testid="clusterBasics__showCloudProviderUnsupportedAzureWarning"]')?.element?.textContent;
expect(cloudProviderUnsupportedAzureWarningMessage).toBe(warningMessage);
});
it.each([
['edit', true, true, '%cluster.banner.cloudProviderMigrateAzure%'],
['edit', false, true, undefined],
['edit', true, false, undefined],
['create', true, true, undefined],
['view', true, true, undefined],
])('should display Azure Migration warning message', (mode, showCloudProvider, canAzureMigrateOnEdit, warningMessage) => {
const wrapper = createBasicsTab('v1.31.0+rke2r1', {}, {
mode,
showCloudProvider,
canAzureMigrateOnEdit,
isAzureProviderUnsupported: true,
});
const cloudProviderMigrateAzureWarningMessage = wrapper.find('[data-testid="clusterBasics__showCloudProviderMigrateAzureWarning"]')?.element?.textContent;
expect(cloudProviderMigrateAzureWarningMessage).toBe(warningMessage);
});
it.each([
['create', true, false],
['edit', false, true],
['edit', true, false],
['view', true, false],
])('should disable Cloud Provider', (mode, canAzureMigrateOnEdit, disabled) => {
const wrapper = createBasicsTab('v1.31.0+rke2r1', {}, {
mode,
showCloudProvider: true,
canAzureMigrateOnEdit,
});
const cloudProvider = wrapper.find('[data-testid="clusterBasics__cloudProvider"]');
expect(cloudProvider.props().disabled).toBe(disabled);
});
});

View File

@ -22,6 +22,7 @@ const defaultStubs = {
BadgeState: true,
Checkbox: true,
ClusterMembershipEditor: true,
ClusterAppearance: true,
DrainOptions: true,
LabeledInput: true,
Labels: true,
@ -53,11 +54,23 @@ const defaultStubs = {
const mockAgentArgs = { 'cloud-provider-name': { options: [], profile: { options: [{ anything: 'yes' }] } } };
const defaultComputed = {
appsOSWarning() {
return false;
},
showForm() {
return true;
},
versionOptions() {
return [
{
id: 'v1.31.0+rke2r1', value: 'v1.31.0+rke2r1', serverArgs: {}, agentArgs: mockAgentArgs, charts: {}
},
{
id: 'v1.30.0+rke2r1', value: 'v1.30.0+rke2r1', serverArgs: {}, agentArgs: mockAgentArgs, charts: {}
},
{
id: 'v1.29.1+rke2r1', value: 'v1.29.1+rke2r1', serverArgs: {}, agentArgs: mockAgentArgs, charts: {}
},
{
id: 'v1.25.0+rke2r1', value: 'v1.25.0+rke2r1', serverArgs: {}, agentArgs: mockAgentArgs, charts: {}
},
@ -332,4 +345,196 @@ describe('component: rke2', () => {
expect(agent.element).toBeDefined();
});
});
it.each([
['v1.25.0+k3s1', 'azure', true],
['v1.31.0+k3s1', 'harvester', true],
['v1.29.0+k3s1', 'harvester', false],
])('should set isAzureProviderUnsupported', (k8s, cloudProvider, value) => {
const wrapper = mount(rke2, {
propsData: {
mode: _CREATE,
value: {
spec: {
...defaultSpec,
kubernetesVersion: k8s
},
agentConfig: { 'cloud-provider-name': cloudProvider }
},
provider: 'custom'
},
data: () => ({}),
computed: defaultComputed,
mocks: {
...defaultMocks,
$store: { dispatch: () => jest.fn(), getters: defaultGetters },
},
stubs: defaultStubs
});
expect((wrapper.vm as any).isAzureProviderUnsupported).toBe(value);
});
it.each([
['edit', 'v1.31.0+k3s1', 'azure', false],
['edit', 'v1.26.0+k3s1', 'azure', false],
['edit', 'v1.28.0+k3s1', 'harvester', false],
['edit', 'v1.28.0+k3s1', 'azure', true],
['create', 'v1.28.0+k3s1', 'azure', false],
['view', 'v1.28.0+k3s1', 'azure', false],
])('should set canAzureMigrateOnEdit', (mode, k8s, liveCloudProvider, value) => {
const wrapper = mount(rke2, {
propsData: {
mode,
liveValue: {
spec: {
...defaultSpec,
kubernetesVersion: k8s
},
agentConfig: { 'cloud-provider-name': liveCloudProvider }
},
value: {
spec: {
...defaultSpec,
kubernetesVersion: k8s
},
agentConfig: { 'cloud-provider-name': liveCloudProvider }
},
provider: 'custom'
},
data: () => ({}),
computed: defaultComputed,
mocks: {
...defaultMocks,
$store: { dispatch: () => jest.fn(), getters: defaultGetters },
},
stubs: defaultStubs
});
expect((wrapper.vm as any).canAzureMigrateOnEdit).toBe(value);
});
it.each([
['', 'v1.32.0+rke2r1', 'amazon', 'v1.32.0+rke2r1'],
['', 'v1.29.0+rke2r1', 'amazon', 'v1.29.0+rke2r1'],
['', 'v1.29.0+rke2r1', 'azure', 'v1.29.0+rke2r1'],
['not', 'v1.31.0+rke2r1', 'azure', undefined],
])('should %p include version %p if Cloud Provider is %p', async(_, k8s, liveCloudProvider, value) => {
const wrapper = mount(rke2, {
propsData: {
mode: 'create',
value: {
spec: {
...defaultSpec,
kubernetesVersion: k8s
},
agentConfig: { 'cloud-provider-name': liveCloudProvider }
},
provider: 'custom'
},
data: () => ({}),
computed: {
appsOSWarning: () => false,
showForm: () => false,
},
mocks: {
...defaultMocks,
$store: { dispatch: () => jest.fn(), getters: defaultGetters },
},
stubs: defaultStubs
});
wrapper.setData({
rke2Versions: [{
id: k8s,
version: k8s,
serverArgs: true
}]
});
expect((wrapper.vm as any).versionOptions[0]?.value).toBe(value);
});
it.each([
['enable', 'v1.28.0+rke2r1', false],
['disable', 'v1.32.0+rke2r1', true],
])('should %p Azure provider option if version is %p', async(_, k8s, value) => {
const wrapper = mount(rke2, {
propsData: {
mode: 'create',
value: {
spec: {
...defaultSpec,
kubernetesVersion: k8s
},
agentConfig: { 'cloud-provider-name': 'azure' }
},
provider: 'custom'
},
data: () => ({
agentArgs: {
'cloud-provider-name': {
options: [
'azure',
'amazon'
]
}
}
}),
computed: defaultComputed,
mocks: {
...defaultMocks,
$store: { dispatch: () => jest.fn(), getters: defaultGetters },
},
stubs: defaultStubs
});
const azureOption = (wrapper.vm as any).cloudProviderOptions.find((o: any) => o.value === 'azure');
expect(azureOption.disabled).toBe(value);
});
it.each([
['enable', 'azure', 'v1.28.0+rke2r1', false], // azure provider / current
['enable', 'external', 'v1.28.0+rke2r1', false], // external provider
['enable', 'azure', 'v1.26.0+rke2r1', false], // version mismatch
['disable', 'amazon', 'v1.26.0+rke2r1', true],
['enable', '', 'v1.28.0+rke2r1', true], // default provider
])('should %p provider option %p in edit mode if live provider is Azure and 1.27 <= k8s < 1.30', async(_, cloudProvider, k8s, value) => {
const wrapper = mount(rke2, {
propsData: {
mode: 'edit',
value: {
spec: {
...defaultSpec,
kubernetesVersion: k8s
},
agentConfig: { 'cloud-provider-name': 'azure' }
},
provider: 'custom'
},
data: () => ({
canAzureMigrateOnEdit: true,
agentArgs: {
'cloud-provider-name': {
options: [
'azure',
'amazon',
'external'
]
}
}
}),
computed: defaultComputed,
mocks: {
...defaultMocks,
$store: { dispatch: () => jest.fn(), getters: defaultGetters },
},
stubs: defaultStubs
});
const azureOption = (wrapper.vm as any).cloudProviderOptions.find((o: any) => o.value === cloudProvider);
expect(azureOption.disabled).toBe(value);
});
});

View File

@ -89,6 +89,8 @@ const NODE_TOTAL = {
const CLUSTER_AGENT_CUSTOMIZATION = 'clusterAgentDeploymentCustomization';
const FLEET_AGENT_CUSTOMIZATION = 'fleetAgentDeploymentCustomization';
const isAzureK8sUnsupported = (version) => semver.gte(version, '1.30.0');
export default {
components: {
AgentEnv,
@ -311,6 +313,7 @@ export default {
const cur = this.liveValue?.spec?.kubernetesVersion || '';
const existingRke2 = this.mode === _EDIT && cur.includes('rke2');
const existingK3s = this.mode === _EDIT && cur.includes('k3s');
const isAzure = this.agentConfig?.['cloud-provider-name'] === 'azure';
let allValidRke2Versions = this.getAllOptionsAfterCurrentVersion(this.rke2Versions, (existingRke2 ? cur : null), this.defaultRke2);
let allValidK3sVersions = this.getAllOptionsAfterCurrentVersion(this.k3sVersions, (existingK3s ? cur : null), this.defaultK3s);
@ -323,6 +326,11 @@ export default {
allValidK3sVersions = this.filterOutDeprecatedPatchVersions(allValidK3sVersions, cur);
}
if (isAzure) {
allValidRke2Versions = allValidRke2Versions.filter((v) => !isAzureK8sUnsupported(v.value));
allValidK3sVersions = allValidK3sVersions.filter((v) => !isAzureK8sUnsupported(v.value));
}
const showRke2 = allValidRke2Versions.length && !existingK3s;
const showK3s = allValidK3sVersions.length && !existingRke2;
const out = [];
@ -594,8 +602,9 @@ export default {
cloudProviderOptions() {
const out = [{
label: this.$store.getters['i18n/t']('cluster.rke2.cloudProvider.defaultValue.label'),
value: '',
label: this.$store.getters['i18n/t']('cluster.rke2.cloudProvider.defaultValue.label'),
value: '',
disabled: this.canAzureMigrateOnEdit
}];
if (!!this.agentArgs['cloud-provider-name']?.options) {
@ -607,12 +616,22 @@ export default {
// If we have a preferred provider... only show default, preferred and external
const isPreferred = opt === preferred;
const isExternal = opt === 'external';
const isAzure = opt === 'azure';
let disabled = false;
if ((this.isHarvesterExternalCredential || this.isHarvesterIncompatible) && isPreferred) {
disabled = true;
}
if (isAzure && isAzureK8sUnsupported(this.value.spec.kubernetesVersion)) {
disabled = true;
}
if (!isAzure && !isExternal && this.canAzureMigrateOnEdit) {
disabled = true;
}
if (showAllOptions || isPreferred || isExternal) {
out.push({
label: this.$store.getters['i18n/withFallback'](`cluster.cloudProvider."${ opt }".label`, null, opt),
@ -632,6 +651,22 @@ export default {
return out;
},
isAzureProviderUnsupported() {
return isAzureK8sUnsupported(this.value.spec.kubernetesVersion) || this.agentConfig['cloud-provider-name'] === 'azure';
},
canAzureMigrateOnEdit() {
if (!this.isEdit) {
return false;
}
const isAzureLiveProvider = this.liveValue.agentConfig['cloud-provider-name'] === 'azure';
return isAzureLiveProvider &&
semver.gte(this.liveValue?.spec?.kubernetesVersion, '1.27.0') &&
semver.lt(this.liveValue?.spec?.kubernetesVersion, '1.30.0');
},
canManageMembers() {
return canViewClusterMembershipEditor(this.$store);
},
@ -2239,6 +2274,8 @@ export default {
:show-cni="showCni"
:show-cloud-provider="showCloudProvider"
:cloud-provider-options="cloudProviderOptions"
:is-azure-provider-unsupported="isAzureProviderUnsupported"
:can-azure-migrate-on-edit="canAzureMigrateOnEdit"
@cilium-values-changed="handleCiliumValuesChanged"
@enabled-system-services-changed="handleEnabledSystemServicesChanged"
@kubernetes-changed="handleKubernetesChange"

View File

@ -11,7 +11,7 @@ import LabeledSelect from '@shell/components/form/LabeledSelect';
import YamlEditor from '@shell/components/YamlEditor';
import { LEGACY } from '@shell/store/features';
import semver from 'semver';
import { _EDIT } from '@shell/config/query-params';
import { _CREATE, _EDIT } from '@shell/config/query-params';
const HARVESTER = 'harvester';
@ -106,6 +106,14 @@ export default {
cloudProviderOptions: {
type: Array,
required: true
},
isAzureProviderUnsupported: {
type: Boolean,
required: true
},
canAzureMigrateOnEdit: {
type: Boolean,
required: true
}
},
@ -377,9 +385,11 @@ export default {
},
canNotEditCloudProvider() {
const canNotEdit = this.isEdit;
if (!this.isEdit) {
return false;
}
return canNotEdit;
return !this.canAzureMigrateOnEdit;
},
/**
@ -387,6 +397,20 @@ export default {
*/
showCloudProviderAmazonAdditionalConfigWarning() {
return !!semver.gte(this.value.spec.kubernetesVersion, 'v1.27.0') && this.agentConfig?.['cloud-provider-name'] === 'aws';
},
/**
* Display warning about unsupported Azure provider if k8s >= 1.30
*/
showCloudProviderUnsupportedAzureWarning() {
return this.showCloudProvider && this.mode === _CREATE && this.isAzureProviderUnsupported;
},
/**
* Display warning about Azure provider migration from k8s versions >= 1.27 to External provider
*/
showCloudProviderMigrateAzureWarning() {
return this.showCloudProvider && this.mode === _EDIT && this.canAzureMigrateOnEdit;
}
},
@ -423,6 +447,20 @@ export default {
v-clean-html="t('cluster.harvester.warning.cloudProvider.incompatible', null, true)"
/>
</Banner>
<Banner
v-if="showCloudProviderUnsupportedAzureWarning"
color="warning"
data-testid="clusterBasics__showCloudProviderUnsupportedAzureWarning"
>
<span v-clean-html="t('cluster.banner.cloudProviderUnsupportedAzure', {}, true)" />
</Banner>
<Banner
v-if="showCloudProviderMigrateAzureWarning"
color="warning"
data-testid="clusterBasics__showCloudProviderMigrateAzureWarning"
>
<span v-clean-html="t('cluster.banner.cloudProviderMigrateAzure', {}, true)" />
</Banner>
<Banner
v-if="showCloudProviderAmazonAdditionalConfigWarning"
color="warning"
@ -461,6 +499,7 @@ export default {
<LabeledSelect
v-if="agentConfig"
v-model="agentConfig['cloud-provider-name']"
data-testid="clusterBasics__cloudProvider"
:mode="mode"
:disabled="canNotEditCloudProvider"
:options="cloudProviderOptions"