diff --git a/Makefile b/Makefile index 1dbf260fc93b..f82ebfab3145 100644 --- a/Makefile +++ b/Makefile @@ -344,6 +344,7 @@ create-aks-cluster: $(KUSTOMIZE) $(ENVSUBST) $(KUBECTL) ## Create a aks cluster. create-cluster: ## Create a workload development Kubernetes cluster on Azure in a kind management cluster. EXP_CLUSTER_RESOURCE_SET=true \ EXP_MACHINE_POOL=true \ + EXP_EDGEZONE=true \ $(MAKE) create-management-cluster \ create-workload-cluster @@ -712,7 +713,7 @@ kind-create: $(KUBECTL) ## Create capz kind cluster if needed. .PHONY: tilt-up tilt-up: install-tools kind-create ## Start tilt and build kind cluster if needed. - CLUSTER_TOPOLOGY=true EXP_CLUSTER_RESOURCE_SET=true EXP_MACHINE_POOL=true EXP_KUBEADM_BOOTSTRAP_FORMAT_IGNITION=true tilt up + CLUSTER_TOPOLOGY=true EXP_CLUSTER_RESOURCE_SET=true EXP_MACHINE_POOL=true EXP_KUBEADM_BOOTSTRAP_FORMAT_IGNITION=true EXP_EDGEZONE=true tilt up .PHONY: delete-cluster delete-cluster: delete-workload-cluster ## Deletes the example kind cluster "capz". diff --git a/api/v1alpha3/azurecluster_conversion.go b/api/v1alpha3/azurecluster_conversion.go index a566e6f4494e..8142382027f7 100644 --- a/api/v1alpha3/azurecluster_conversion.go +++ b/api/v1alpha3/azurecluster_conversion.go @@ -116,6 +116,9 @@ func (src *AzureCluster) ConvertTo(dstRaw conversion.Hub) error { // Restore list of virtual network peerings dst.Spec.NetworkSpec.Vnet.Peerings = restored.Spec.NetworkSpec.Vnet.Peerings + // Restore ExtendedLocation properties + dst.Spec.ExtendedLocation = restored.Spec.ExtendedLocation + return nil } diff --git a/api/v1alpha4/azurecluster_conversion.go b/api/v1alpha4/azurecluster_conversion.go index fe077eb751a1..d769e9b655a3 100644 --- a/api/v1alpha4/azurecluster_conversion.go +++ b/api/v1alpha4/azurecluster_conversion.go @@ -41,6 +41,9 @@ func (src *AzureCluster) ConvertTo(dstRaw conversion.Hub) error { // Restore list of virtual network peerings dst.Spec.NetworkSpec.Vnet.Peerings = restored.Spec.NetworkSpec.Vnet.Peerings + // Restore ExtendedLocation properties + dst.Spec.ExtendedLocation = restored.Spec.ExtendedLocation + // Restore API Server LB IP tags. for _, restoredFrontendIP := range restored.Spec.NetworkSpec.APIServerLB.FrontendIPs { for i, dstFrontendIP := range dst.Spec.NetworkSpec.APIServerLB.FrontendIPs { diff --git a/api/v1beta1/azurecluster_validation.go b/api/v1beta1/azurecluster_validation.go index 0e3877091644..582094bc010b 100644 --- a/api/v1beta1/azurecluster_validation.go +++ b/api/v1beta1/azurecluster_validation.go @@ -27,6 +27,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/utils/pointer" + "sigs.k8s.io/cluster-api-provider-azure/feature" ) const ( @@ -96,6 +97,11 @@ func (c *AzureCluster) validateClusterSpec(old *AzureCluster) field.ErrorList { allErrs = append(allErrs, validateCloudProviderConfigOverrides(c.Spec.CloudProviderConfigOverrides, oldCloudProviderConfigOverrides, field.NewPath("spec").Child("cloudProviderConfigOverrides"))...) + // If ClusterSpec has non-nil ExtendedLocation field but not enable EdgeZone feature gate flag, ClusterSpec validation failed. + if !feature.Gates.Enabled(feature.EdgeZone) && c.Spec.ExtendedLocation != nil { + allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "ExtendedLocation"), "can be set only if the EdgeZone feature flag is enabled")) + } + if err := validateBastionSpec(c.Spec.BastionSpec, field.NewPath("spec").Child("azureBastion").Child("bastionSpec")); err != nil { allErrs = append(allErrs, err) } diff --git a/api/v1beta1/azurecluster_validation_test.go b/api/v1beta1/azurecluster_validation_test.go index a3fc01b56454..bb57805eb6b9 100644 --- a/api/v1beta1/azurecluster_validation_test.go +++ b/api/v1beta1/azurecluster_validation_test.go @@ -1505,3 +1505,27 @@ func TestServiceEndpointsLackRequiredFieldLocations(t *testing.T) { g.Expect(errs[0].Error()).To(ContainSubstring("locations are required for all service endpoints")) }) } + +func TestClusterWithExtendedLocationInvalid(t *testing.T) { + g := NewWithT(t) + + type test struct { + name string + cluster *AzureCluster + } + + testCase := test{ + name: "azurecluster spec with extended location but not enable EdgeZone feature gate flag", + cluster: createValidCluster(), + } + + testCase.cluster.Spec.ExtendedLocation = &ExtendedLocationSpec{ + Name: "rr4", + Type: "EdgeZone", + } + + t.Run(testCase.name, func(t *testing.T) { + err := testCase.cluster.validateClusterSpec(nil) + g.Expect(err).NotTo(BeNil()) + }) +} diff --git a/api/v1beta1/azurecluster_webhook_test.go b/api/v1beta1/azurecluster_webhook_test.go index b30e487be0a1..0bc42440d732 100644 --- a/api/v1beta1/azurecluster_webhook_test.go +++ b/api/v1beta1/azurecluster_webhook_test.go @@ -94,6 +94,18 @@ func TestAzureCluster_ValidateCreate(t *testing.T) { }(), wantErr: true, }, + { + name: "azurecluster with ExtendedLocation and false EdgeZone feature flag", + cluster: func() *AzureCluster { + cluster := createValidCluster() + cluster.Spec.ExtendedLocation = &ExtendedLocationSpec{ + Name: "rr4", + Type: "EdgeZone", + } + return cluster + }(), + wantErr: true, + }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { diff --git a/api/v1beta1/types_class.go b/api/v1beta1/types_class.go index 08e2f6d1c5b0..04f33564d264 100644 --- a/api/v1beta1/types_class.go +++ b/api/v1beta1/types_class.go @@ -25,6 +25,10 @@ type AzureClusterClassSpec struct { Location string `json:"location"` + // ExtendedLocation is an optional set of ExtendedLocation properties for clusters on Azure public MEC. + // +optional + ExtendedLocation *ExtendedLocationSpec `json:"extendedLocation,omitempty"` + // AdditionalTags is an optional set of tags to add to Azure resources managed by the Azure provider, in addition to the // ones added by default. // +optional @@ -52,6 +56,16 @@ type AzureClusterClassSpec struct { CloudProviderConfigOverrides *CloudProviderConfigOverrides `json:"cloudProviderConfigOverrides,omitempty"` } +// ExtendedLocationSpec defines the ExtendedLocation properties to enable CAPZ for Azure public MEC. +type ExtendedLocationSpec struct { + // Name defines the name for the extended location. + Name string `json:"name"` + + // Type defines the type for the extended location. + // +kubebuilder:validation:Enum=EdgeZone + Type string `json:"type"` +} + // NetworkClassSpec defines the NetworkSpec properties that may be shared across several Azure clusters. type NetworkClassSpec struct { // PrivateDNSZoneName defines the zone name for the Azure Private DNS. diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index c165dbde97fd..8d24ee954fc2 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -344,6 +344,11 @@ func (in *AzureCluster) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AzureClusterClassSpec) DeepCopyInto(out *AzureClusterClassSpec) { *out = *in + if in.ExtendedLocation != nil { + in, out := &in.ExtendedLocation, &out.ExtendedLocation + *out = new(ExtendedLocationSpec) + **out = **in + } if in.AdditionalTags != nil { in, out := &in.AdditionalTags, &out.AdditionalTags *out = make(Tags, len(*in)) @@ -1737,6 +1742,21 @@ func (in *DiskEncryptionSetParameters) DeepCopy() *DiskEncryptionSetParameters { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtendedLocationSpec) DeepCopyInto(out *ExtendedLocationSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtendedLocationSpec. +func (in *ExtendedLocationSpec) DeepCopy() *ExtendedLocationSpec { + if in == nil { + return nil + } + out := new(ExtendedLocationSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FrontendIP) DeepCopyInto(out *FrontendIP) { *out = *in diff --git a/azure/converters/extendedlocation.go b/azure/converters/extendedlocation.go new file mode 100644 index 000000000000..828875b84e04 --- /dev/null +++ b/azure/converters/extendedlocation.go @@ -0,0 +1,48 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package converters + +import ( + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2021-11-01/compute" + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-08-01/network" + "github.com/Azure/go-autorest/autorest/to" + infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" +) + +// ExtendedLocationToNetworkSDK converts infrav1.ExtendedLocationSpec to network.ExtendedLocation. +func ExtendedLocationToNetworkSDK(src *infrav1.ExtendedLocationSpec) *network.ExtendedLocation { + if src == nil { + return nil + } else { + return &network.ExtendedLocation{ + Name: to.StringPtr(src.Name), + Type: network.ExtendedLocationTypes(src.Type), + } + } +} + +// ExtendedLocationToComputeSDK converts infrav1.ExtendedLocationSpec to compute.ExtendedLocation. +func ExtendedLocationToComputeSDK(src *infrav1.ExtendedLocationSpec) *compute.ExtendedLocation { + if src == nil { + return nil + } else { + return &compute.ExtendedLocation{ + Name: to.StringPtr(src.Name), + Type: compute.ExtendedLocationTypes(src.Type), + } + } +} diff --git a/azure/converters/extendedlocation_test.go b/azure/converters/extendedlocation_test.go new file mode 100644 index 000000000000..31da55d9a859 --- /dev/null +++ b/azure/converters/extendedlocation_test.go @@ -0,0 +1,91 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package converters + +import ( + "reflect" + "testing" + + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2021-11-01/compute" + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-08-01/network" + "github.com/Azure/go-autorest/autorest/to" + infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" +) + +func TestExtendedLocationToNetworkSDK(t *testing.T) { + tests := []struct { + name string + args *infrav1.ExtendedLocationSpec + want *network.ExtendedLocation + }{ + { + name: "normal extendedLocation instance", + args: &infrav1.ExtendedLocationSpec{ + Name: "value", + Type: "Edge", + }, + want: &network.ExtendedLocation{ + Name: to.StringPtr("value"), + Type: network.ExtendedLocationTypes("Edge"), + }, + }, + { + name: "nil extendedLocation properties", + args: nil, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ExtendedLocationToNetworkSDK(tt.args); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ExtendedLocationToNetworkSDK() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestExtendedLocationToComputeSDK(t *testing.T) { + tests := []struct { + name string + args *infrav1.ExtendedLocationSpec + want *compute.ExtendedLocation + }{ + { + name: "normal extendedLocation instance", + args: &infrav1.ExtendedLocationSpec{ + Name: "value", + Type: "Edge", + }, + want: &compute.ExtendedLocation{ + Name: to.StringPtr("value"), + Type: compute.ExtendedLocationTypes("Edge"), + }, + }, + { + name: "nil extendedLocation properties", + args: nil, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ExtendedLocationToComputeSDK(tt.args); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ExtendedLocationToComputeSDK() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/azure/interfaces.go b/azure/interfaces.go index c43591e2e0fd..d6a1f6021b3a 100644 --- a/azure/interfaces.go +++ b/azure/interfaces.go @@ -75,6 +75,9 @@ type ClusterDescriber interface { ResourceGroup() string ClusterName() string Location() string + ExtendedLocation() *infrav1.ExtendedLocationSpec + ExtendedLocationName() string + ExtendedLocationType() string AdditionalTags() infrav1.Tags AvailabilitySetEnabled() bool CloudProviderConfigOverrides() *infrav1.CloudProviderConfigOverrides diff --git a/azure/mock_azure/azure_mock.go b/azure/mock_azure/azure_mock.go index 1323554c8d0b..e75637102381 100644 --- a/azure/mock_azure/azure_mock.go +++ b/azure/mock_azure/azure_mock.go @@ -690,6 +690,48 @@ func (mr *MockClusterDescriberMockRecorder) ClusterName() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClusterName", reflect.TypeOf((*MockClusterDescriber)(nil).ClusterName)) } +// ExtendedLocation mocks base method. +func (m *MockClusterDescriber) ExtendedLocation() *v1beta1.ExtendedLocationSpec { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocation") + ret0, _ := ret[0].(*v1beta1.ExtendedLocationSpec) + return ret0 +} + +// ExtendedLocation indicates an expected call of ExtendedLocation. +func (mr *MockClusterDescriberMockRecorder) ExtendedLocation() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocation", reflect.TypeOf((*MockClusterDescriber)(nil).ExtendedLocation)) +} + +// ExtendedLocationName mocks base method. +func (m *MockClusterDescriber) ExtendedLocationName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationName indicates an expected call of ExtendedLocationName. +func (mr *MockClusterDescriberMockRecorder) ExtendedLocationName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationName", reflect.TypeOf((*MockClusterDescriber)(nil).ExtendedLocationName)) +} + +// ExtendedLocationType mocks base method. +func (m *MockClusterDescriber) ExtendedLocationType() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationType") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationType indicates an expected call of ExtendedLocationType. +func (mr *MockClusterDescriberMockRecorder) ExtendedLocationType() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationType", reflect.TypeOf((*MockClusterDescriber)(nil).ExtendedLocationType)) +} + // FailureDomains mocks base method. func (m *MockClusterDescriber) FailureDomains() []string { m.ctrl.T.Helper() @@ -1090,6 +1132,48 @@ func (mr *MockClusterScoperMockRecorder) ControlPlaneSubnet() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ControlPlaneSubnet", reflect.TypeOf((*MockClusterScoper)(nil).ControlPlaneSubnet)) } +// ExtendedLocation mocks base method. +func (m *MockClusterScoper) ExtendedLocation() *v1beta1.ExtendedLocationSpec { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocation") + ret0, _ := ret[0].(*v1beta1.ExtendedLocationSpec) + return ret0 +} + +// ExtendedLocation indicates an expected call of ExtendedLocation. +func (mr *MockClusterScoperMockRecorder) ExtendedLocation() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocation", reflect.TypeOf((*MockClusterScoper)(nil).ExtendedLocation)) +} + +// ExtendedLocationName mocks base method. +func (m *MockClusterScoper) ExtendedLocationName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationName indicates an expected call of ExtendedLocationName. +func (mr *MockClusterScoperMockRecorder) ExtendedLocationName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationName", reflect.TypeOf((*MockClusterScoper)(nil).ExtendedLocationName)) +} + +// ExtendedLocationType mocks base method. +func (m *MockClusterScoper) ExtendedLocationType() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationType") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationType indicates an expected call of ExtendedLocationType. +func (mr *MockClusterScoperMockRecorder) ExtendedLocationType() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationType", reflect.TypeOf((*MockClusterScoper)(nil).ExtendedLocationType)) +} + // FailureDomains mocks base method. func (m *MockClusterScoper) FailureDomains() []string { m.ctrl.T.Helper() @@ -1475,6 +1559,48 @@ func (mr *MockManagedClusterScoperMockRecorder) ClusterName() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClusterName", reflect.TypeOf((*MockManagedClusterScoper)(nil).ClusterName)) } +// ExtendedLocation mocks base method. +func (m *MockManagedClusterScoper) ExtendedLocation() *v1beta1.ExtendedLocationSpec { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocation") + ret0, _ := ret[0].(*v1beta1.ExtendedLocationSpec) + return ret0 +} + +// ExtendedLocation indicates an expected call of ExtendedLocation. +func (mr *MockManagedClusterScoperMockRecorder) ExtendedLocation() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocation", reflect.TypeOf((*MockManagedClusterScoper)(nil).ExtendedLocation)) +} + +// ExtendedLocationName mocks base method. +func (m *MockManagedClusterScoper) ExtendedLocationName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationName indicates an expected call of ExtendedLocationName. +func (mr *MockManagedClusterScoperMockRecorder) ExtendedLocationName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationName", reflect.TypeOf((*MockManagedClusterScoper)(nil).ExtendedLocationName)) +} + +// ExtendedLocationType mocks base method. +func (m *MockManagedClusterScoper) ExtendedLocationType() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationType") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationType indicates an expected call of ExtendedLocationType. +func (mr *MockManagedClusterScoperMockRecorder) ExtendedLocationType() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationType", reflect.TypeOf((*MockManagedClusterScoper)(nil).ExtendedLocationType)) +} + // FailureDomains mocks base method. func (m *MockManagedClusterScoper) FailureDomains() []string { m.ctrl.T.Helper() diff --git a/azure/scope/cluster.go b/azure/scope/cluster.go index bcd6eb574542..f0edb2b19717 100644 --- a/azure/scope/cluster.go +++ b/azure/scope/cluster.go @@ -145,29 +145,31 @@ func (s *ClusterScope) PublicIPSpecs() []azure.ResourceSpecGetter { if s.ControlPlaneOutboundLB() != nil { for _, ip := range s.ControlPlaneOutboundLB().FrontendIPs { controlPlaneOutboundIPSpecs = append(controlPlaneOutboundIPSpecs, &publicips.PublicIPSpec{ - Name: ip.PublicIP.Name, - ResourceGroup: s.ResourceGroup(), - ClusterName: s.ClusterName(), - DNSName: "", // Set to default value - IsIPv6: false, // Set to default value - Location: s.Location(), - FailureDomains: s.FailureDomains(), - AdditionalTags: s.AdditionalTags(), + Name: ip.PublicIP.Name, + ResourceGroup: s.ResourceGroup(), + ClusterName: s.ClusterName(), + DNSName: "", // Set to default value + IsIPv6: false, // Set to default value + Location: s.Location(), + ExtendedLocation: s.ExtendedLocation(), + FailureDomains: s.FailureDomains(), + AdditionalTags: s.AdditionalTags(), }) } } } else { controlPlaneOutboundIPSpecs = []azure.ResourceSpecGetter{ &publicips.PublicIPSpec{ - Name: s.APIServerPublicIP().Name, - ResourceGroup: s.ResourceGroup(), - DNSName: s.APIServerPublicIP().DNSName, - IsIPv6: false, // Currently azure requires an IPv4 lb rule to enable IPv6 - ClusterName: s.ClusterName(), - Location: s.Location(), - FailureDomains: s.FailureDomains(), - AdditionalTags: s.AdditionalTags(), - IPTags: s.APIServerPublicIP().IPTags, + Name: s.APIServerPublicIP().Name, + ResourceGroup: s.ResourceGroup(), + DNSName: s.APIServerPublicIP().DNSName, + IsIPv6: false, // Currently azure requires an IPv4 lb rule to enable IPv6 + ClusterName: s.ClusterName(), + Location: s.Location(), + ExtendedLocation: s.ExtendedLocation(), + FailureDomains: s.FailureDomains(), + AdditionalTags: s.AdditionalTags(), + IPTags: s.APIServerPublicIP().IPTags, }, } } @@ -177,14 +179,15 @@ func (s *ClusterScope) PublicIPSpecs() []azure.ResourceSpecGetter { if s.NodeOutboundLB() != nil { for _, ip := range s.NodeOutboundLB().FrontendIPs { publicIPSpecs = append(publicIPSpecs, &publicips.PublicIPSpec{ - Name: ip.PublicIP.Name, - ResourceGroup: s.ResourceGroup(), - ClusterName: s.ClusterName(), - DNSName: "", // Set to default value - IsIPv6: false, // Set to default value - Location: s.Location(), - FailureDomains: s.FailureDomains(), - AdditionalTags: s.AdditionalTags(), + Name: ip.PublicIP.Name, + ResourceGroup: s.ResourceGroup(), + ClusterName: s.ClusterName(), + DNSName: "", // Set to default value + IsIPv6: false, // Set to default value + Location: s.Location(), + ExtendedLocation: s.ExtendedLocation(), + FailureDomains: s.FailureDomains(), + AdditionalTags: s.AdditionalTags(), }) } } @@ -237,6 +240,7 @@ func (s *ClusterScope) LBSpecs() []azure.ResourceSpecGetter { SubscriptionID: s.SubscriptionID(), ClusterName: s.ClusterName(), Location: s.Location(), + ExtendedLocation: s.ExtendedLocation(), VNetName: s.Vnet().Name, VNetResourceGroup: s.Vnet().ResourceGroup, SubnetName: s.ControlPlaneSubnet().Name, @@ -259,6 +263,7 @@ func (s *ClusterScope) LBSpecs() []azure.ResourceSpecGetter { SubscriptionID: s.SubscriptionID(), ClusterName: s.ClusterName(), Location: s.Location(), + ExtendedLocation: s.ExtendedLocation(), VNetName: s.Vnet().Name, VNetResourceGroup: s.Vnet().ResourceGroup, FrontendIPConfigs: s.NodeOutboundLB().FrontendIPs, @@ -279,6 +284,7 @@ func (s *ClusterScope) LBSpecs() []azure.ResourceSpecGetter { SubscriptionID: s.SubscriptionID(), ClusterName: s.ClusterName(), Location: s.Location(), + ExtendedLocation: s.ExtendedLocation(), VNetName: s.Vnet().Name, VNetResourceGroup: s.Vnet().ResourceGroup, FrontendIPConfigs: s.ControlPlaneOutboundLB().FrontendIPs, @@ -444,12 +450,13 @@ func (s *ClusterScope) VnetPeeringSpecs() []azure.ResourceSpecGetter { // VNetSpec returns the virtual network spec. func (s *ClusterScope) VNetSpec() azure.ResourceSpecGetter { return &virtualnetworks.VNetSpec{ - ResourceGroup: s.Vnet().ResourceGroup, - Name: s.Vnet().Name, - CIDRs: s.Vnet().CIDRBlocks, - Location: s.Location(), - ClusterName: s.ClusterName(), - AdditionalTags: s.AdditionalTags(), + ResourceGroup: s.Vnet().ResourceGroup, + Name: s.Vnet().Name, + CIDRs: s.Vnet().CIDRBlocks, + ExtendedLocation: s.ExtendedLocation(), + Location: s.Location(), + ClusterName: s.ClusterName(), + AdditionalTags: s.AdditionalTags(), } } @@ -736,6 +743,27 @@ func (s *ClusterScope) CloudProviderConfigOverrides() *infrav1.CloudProviderConf return s.AzureCluster.Spec.CloudProviderConfigOverrides } +// ExtendedLocationName returns ExtendedLocation name for the cluster. +func (s *ClusterScope) ExtendedLocationName() string { + if s.ExtendedLocation() == nil { + return "" + } + return s.ExtendedLocation().Name +} + +// ExtendedLocationType returns ExtendedLocation type for the cluster. +func (s *ClusterScope) ExtendedLocationType() string { + if s.ExtendedLocation() == nil { + return "" + } + return s.ExtendedLocation().Type +} + +// ExtendedLocation returns the cluster extendedLocation. +func (s *ClusterScope) ExtendedLocation() *infrav1.ExtendedLocationSpec { + return s.AzureCluster.Spec.ExtendedLocation +} + // GenerateFQDN generates a fully qualified domain name, based on a hash, cluster name and cluster location. func (s *ClusterScope) GenerateFQDN(ipName string) string { h := fnv.New32a() diff --git a/azure/scope/cluster_test.go b/azure/scope/cluster_test.go index 93dd0176b83f..4bbac99a3888 100644 --- a/azure/scope/cluster_test.go +++ b/azure/scope/cluster_test.go @@ -2955,3 +2955,159 @@ func TestClusterScope_LBSpecs(t *testing.T) { }) } } + +func TestExtendedLocationName(t *testing.T) { + tests := []struct { + name string + clusterName string + extendedLocation infrav1.ExtendedLocationSpec + }{ + { + name: "Empty extendedLocatioName", + clusterName: "my-cluster", + extendedLocation: infrav1.ExtendedLocationSpec{ + Name: "", + Type: "", + }, + }, + { + name: "Non empty extendedLocationName", + clusterName: "my-cluster", + extendedLocation: infrav1.ExtendedLocationSpec{ + Name: "ex-loc-name", + Type: "ex-loc-type", + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + scheme := runtime.NewScheme() + _ = infrav1.AddToScheme(scheme) + _ = clusterv1.AddToScheme(scheme) + + cluster := &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.clusterName, + Namespace: "default", + }, + } + + azureCluster := &infrav1.AzureCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.clusterName, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "cluster.x-k8s.io/v1beta1", + Kind: "Cluster", + Name: "my-cluster", + }, + }, + }, + Spec: infrav1.AzureClusterSpec{ + AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ + SubscriptionID: "123", + ExtendedLocation: &infrav1.ExtendedLocationSpec{ + Name: tc.extendedLocation.Name, + Type: tc.extendedLocation.Type, + }, + }, + }, + } + + initObjects := []runtime.Object{cluster, azureCluster} + fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(initObjects...).Build() + + clusterScope, err := NewClusterScope(context.TODO(), ClusterScopeParams{ + AzureClients: AzureClients{ + Authorizer: autorest.NullAuthorizer{}, + }, + Cluster: cluster, + AzureCluster: azureCluster, + Client: fakeClient, + }) + + g.Expect(err).NotTo(HaveOccurred()) + got := clusterScope.ExtendedLocationName() + g.Expect(tc.extendedLocation.Name).Should(Equal(got)) + }) + } +} + +func TestExtendedLocationType(t *testing.T) { + tests := []struct { + name string + clusterName string + extendedLocation infrav1.ExtendedLocationSpec + }{ + { + name: "Empty extendedLocatioType", + clusterName: "my-cluster", + extendedLocation: infrav1.ExtendedLocationSpec{ + Name: "", + Type: "", + }, + }, + { + name: "Non empty extendedLocationType", + clusterName: "my-cluster", + extendedLocation: infrav1.ExtendedLocationSpec{ + Name: "ex-loc-name", + Type: "ex-loc-type", + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + scheme := runtime.NewScheme() + _ = infrav1.AddToScheme(scheme) + _ = clusterv1.AddToScheme(scheme) + + cluster := &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.clusterName, + Namespace: "default", + }, + } + + azureCluster := &infrav1.AzureCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.clusterName, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "cluster.x-k8s.io/v1beta1", + Kind: "Cluster", + Name: "my-cluster", + }, + }, + }, + Spec: infrav1.AzureClusterSpec{ + AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ + SubscriptionID: "123", + ExtendedLocation: &infrav1.ExtendedLocationSpec{ + Name: tc.extendedLocation.Name, + Type: tc.extendedLocation.Type, + }, + }, + }, + } + + initObjects := []runtime.Object{cluster, azureCluster} + fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(initObjects...).Build() + + clusterScope, err := NewClusterScope(context.TODO(), ClusterScopeParams{ + AzureClients: AzureClients{ + Authorizer: autorest.NullAuthorizer{}, + }, + Cluster: cluster, + AzureCluster: azureCluster, + Client: fakeClient, + }) + + g.Expect(err).NotTo(HaveOccurred()) + got := clusterScope.ExtendedLocationType() + g.Expect(tc.extendedLocation.Type).Should(Equal(got)) + }) + } +} diff --git a/azure/scope/machine.go b/azure/scope/machine.go index 961eae0412b3..da5586a136ca 100644 --- a/azure/scope/machine.go +++ b/azure/scope/machine.go @@ -149,6 +149,7 @@ func (m *MachineScope) VMSpec() azure.ResourceSpecGetter { spec := &virtualmachines.VMSpec{ Name: m.Name(), Location: m.Location(), + ExtendedLocation: m.ExtendedLocation(), ResourceGroup: m.ResourceGroup(), ClusterName: m.ClusterName(), Role: m.Role(), @@ -192,14 +193,15 @@ func (m *MachineScope) PublicIPSpecs() []azure.ResourceSpecGetter { var specs []azure.ResourceSpecGetter if m.AzureMachine.Spec.AllocatePublicIP { specs = append(specs, &publicips.PublicIPSpec{ - Name: azure.GenerateNodePublicIPName(m.Name()), - ResourceGroup: m.ResourceGroup(), - ClusterName: m.ClusterName(), - DNSName: "", // Set to default value - IsIPv6: false, // Set to default value - Location: m.Location(), - FailureDomains: m.FailureDomains(), - AdditionalTags: m.ClusterScoper.AdditionalTags(), + Name: azure.GenerateNodePublicIPName(m.Name()), + ResourceGroup: m.ResourceGroup(), + ClusterName: m.ClusterName(), + DNSName: "", // Set to default value + IsIPv6: false, // Set to default value + Location: m.Location(), + ExtendedLocation: m.ExtendedLocation(), + FailureDomains: m.FailureDomains(), + AdditionalTags: m.ClusterScoper.AdditionalTags(), }) } return specs @@ -248,6 +250,7 @@ func (m *MachineScope) BuildNICSpec(nicName string, infrav1NetworkInterface infr Name: nicName, ResourceGroup: m.ResourceGroup(), Location: m.Location(), + ExtendedLocation: m.ExtendedLocation(), SubscriptionID: m.SubscriptionID(), MachineName: m.Name(), VNetName: m.Vnet().Name, @@ -491,7 +494,8 @@ func (m *MachineScope) AvailabilitySetSpec() azure.ResourceSpecGetter { // AvailabilitySet returns the availability set for this machine if available. func (m *MachineScope) AvailabilitySet() (string, bool) { - if !m.AvailabilitySetEnabled() { + // AvailabilitySet service is not supported on EdgeZone currently. + if !m.AvailabilitySetEnabled() || m.ExtendedLocation() != nil { return "", false } diff --git a/azure/scope/managedcontrolplane.go b/azure/scope/managedcontrolplane.go index 6bdaed9a41c8..ca6e55677539 100644 --- a/azure/scope/managedcontrolplane.go +++ b/azure/scope/managedcontrolplane.go @@ -154,6 +154,21 @@ func (s *ManagedControlPlaneScope) Location() string { return s.ControlPlane.Spec.Location } +// ExtendedLocation has not been implemented for AzureManagedControlPlane. +func (s *ManagedControlPlaneScope) ExtendedLocation() *infrav1.ExtendedLocationSpec { + return nil +} + +// ExtendedLocationName has not been implemented for AzureManagedControlPlane. +func (s *ManagedControlPlaneScope) ExtendedLocationName() string { + return "" +} + +// ExtendedLocationType has not been implemented for AzureManagedControlPlane. +func (s *ManagedControlPlaneScope) ExtendedLocationType() string { + return "" +} + // AvailabilitySetEnabled is always false for a managed control plane. func (s *ManagedControlPlaneScope) AvailabilitySetEnabled() bool { return false // not applicable for a managed control plane diff --git a/azure/services/agentpools/mock_agentpools/agentpools_mock.go b/azure/services/agentpools/mock_agentpools/agentpools_mock.go index 770d78cc5e17..91eefee9fa95 100644 --- a/azure/services/agentpools/mock_agentpools/agentpools_mock.go +++ b/azure/services/agentpools/mock_agentpools/agentpools_mock.go @@ -219,6 +219,48 @@ func (mr *MockAgentPoolScopeMockRecorder) DeleteLongRunningOperationState(arg0, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLongRunningOperationState", reflect.TypeOf((*MockAgentPoolScope)(nil).DeleteLongRunningOperationState), arg0, arg1, arg2) } +// ExtendedLocation mocks base method. +func (m *MockAgentPoolScope) ExtendedLocation() *v1beta1.ExtendedLocationSpec { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocation") + ret0, _ := ret[0].(*v1beta1.ExtendedLocationSpec) + return ret0 +} + +// ExtendedLocation indicates an expected call of ExtendedLocation. +func (mr *MockAgentPoolScopeMockRecorder) ExtendedLocation() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocation", reflect.TypeOf((*MockAgentPoolScope)(nil).ExtendedLocation)) +} + +// ExtendedLocationName mocks base method. +func (m *MockAgentPoolScope) ExtendedLocationName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationName indicates an expected call of ExtendedLocationName. +func (mr *MockAgentPoolScopeMockRecorder) ExtendedLocationName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationName", reflect.TypeOf((*MockAgentPoolScope)(nil).ExtendedLocationName)) +} + +// ExtendedLocationType mocks base method. +func (m *MockAgentPoolScope) ExtendedLocationType() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationType") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationType indicates an expected call of ExtendedLocationType. +func (mr *MockAgentPoolScopeMockRecorder) ExtendedLocationType() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationType", reflect.TypeOf((*MockAgentPoolScope)(nil).ExtendedLocationType)) +} + // FailureDomains mocks base method. func (m *MockAgentPoolScope) FailureDomains() []string { m.ctrl.T.Helper() diff --git a/azure/services/availabilitysets/mock_availabilitysets/availabilitysets_mock.go b/azure/services/availabilitysets/mock_availabilitysets/availabilitysets_mock.go index d8b48ce5259d..badb8d88505b 100644 --- a/azure/services/availabilitysets/mock_availabilitysets/availabilitysets_mock.go +++ b/azure/services/availabilitysets/mock_availabilitysets/availabilitysets_mock.go @@ -205,6 +205,48 @@ func (mr *MockAvailabilitySetScopeMockRecorder) DeleteLongRunningOperationState( return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLongRunningOperationState", reflect.TypeOf((*MockAvailabilitySetScope)(nil).DeleteLongRunningOperationState), arg0, arg1, arg2) } +// ExtendedLocation mocks base method. +func (m *MockAvailabilitySetScope) ExtendedLocation() *v1beta1.ExtendedLocationSpec { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocation") + ret0, _ := ret[0].(*v1beta1.ExtendedLocationSpec) + return ret0 +} + +// ExtendedLocation indicates an expected call of ExtendedLocation. +func (mr *MockAvailabilitySetScopeMockRecorder) ExtendedLocation() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocation", reflect.TypeOf((*MockAvailabilitySetScope)(nil).ExtendedLocation)) +} + +// ExtendedLocationName mocks base method. +func (m *MockAvailabilitySetScope) ExtendedLocationName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationName indicates an expected call of ExtendedLocationName. +func (mr *MockAvailabilitySetScopeMockRecorder) ExtendedLocationName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationName", reflect.TypeOf((*MockAvailabilitySetScope)(nil).ExtendedLocationName)) +} + +// ExtendedLocationType mocks base method. +func (m *MockAvailabilitySetScope) ExtendedLocationType() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationType") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationType indicates an expected call of ExtendedLocationType. +func (mr *MockAvailabilitySetScopeMockRecorder) ExtendedLocationType() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationType", reflect.TypeOf((*MockAvailabilitySetScope)(nil).ExtendedLocationType)) +} + // FailureDomains mocks base method. func (m *MockAvailabilitySetScope) FailureDomains() []string { m.ctrl.T.Helper() diff --git a/azure/services/bastionhosts/mocks_bastionhosts/bastionhosts_mock.go b/azure/services/bastionhosts/mocks_bastionhosts/bastionhosts_mock.go index 6feb7ce8f9aa..a9913b6de1fe 100644 --- a/azure/services/bastionhosts/mocks_bastionhosts/bastionhosts_mock.go +++ b/azure/services/bastionhosts/mocks_bastionhosts/bastionhosts_mock.go @@ -275,6 +275,48 @@ func (mr *MockBastionScopeMockRecorder) DeleteLongRunningOperationState(arg0, ar return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLongRunningOperationState", reflect.TypeOf((*MockBastionScope)(nil).DeleteLongRunningOperationState), arg0, arg1, arg2) } +// ExtendedLocation mocks base method. +func (m *MockBastionScope) ExtendedLocation() *v1beta1.ExtendedLocationSpec { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocation") + ret0, _ := ret[0].(*v1beta1.ExtendedLocationSpec) + return ret0 +} + +// ExtendedLocation indicates an expected call of ExtendedLocation. +func (mr *MockBastionScopeMockRecorder) ExtendedLocation() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocation", reflect.TypeOf((*MockBastionScope)(nil).ExtendedLocation)) +} + +// ExtendedLocationName mocks base method. +func (m *MockBastionScope) ExtendedLocationName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationName indicates an expected call of ExtendedLocationName. +func (mr *MockBastionScopeMockRecorder) ExtendedLocationName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationName", reflect.TypeOf((*MockBastionScope)(nil).ExtendedLocationName)) +} + +// ExtendedLocationType mocks base method. +func (m *MockBastionScope) ExtendedLocationType() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationType") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationType indicates an expected call of ExtendedLocationType. +func (mr *MockBastionScopeMockRecorder) ExtendedLocationType() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationType", reflect.TypeOf((*MockBastionScope)(nil).ExtendedLocationType)) +} + // FailureDomains mocks base method. func (m *MockBastionScope) FailureDomains() []string { m.ctrl.T.Helper() diff --git a/azure/services/disks/mock_disks/disks_mock.go b/azure/services/disks/mock_disks/disks_mock.go index 6d6e53bb38ca..f9db984cd7aa 100644 --- a/azure/services/disks/mock_disks/disks_mock.go +++ b/azure/services/disks/mock_disks/disks_mock.go @@ -205,6 +205,48 @@ func (mr *MockDiskScopeMockRecorder) DiskSpecs() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DiskSpecs", reflect.TypeOf((*MockDiskScope)(nil).DiskSpecs)) } +// ExtendedLocation mocks base method. +func (m *MockDiskScope) ExtendedLocation() *v1beta1.ExtendedLocationSpec { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocation") + ret0, _ := ret[0].(*v1beta1.ExtendedLocationSpec) + return ret0 +} + +// ExtendedLocation indicates an expected call of ExtendedLocation. +func (mr *MockDiskScopeMockRecorder) ExtendedLocation() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocation", reflect.TypeOf((*MockDiskScope)(nil).ExtendedLocation)) +} + +// ExtendedLocationName mocks base method. +func (m *MockDiskScope) ExtendedLocationName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationName indicates an expected call of ExtendedLocationName. +func (mr *MockDiskScopeMockRecorder) ExtendedLocationName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationName", reflect.TypeOf((*MockDiskScope)(nil).ExtendedLocationName)) +} + +// ExtendedLocationType mocks base method. +func (m *MockDiskScope) ExtendedLocationType() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationType") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationType indicates an expected call of ExtendedLocationType. +func (mr *MockDiskScopeMockRecorder) ExtendedLocationType() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationType", reflect.TypeOf((*MockDiskScope)(nil).ExtendedLocationType)) +} + // FailureDomains mocks base method. func (m *MockDiskScope) FailureDomains() []string { m.ctrl.T.Helper() diff --git a/azure/services/inboundnatrules/mock_inboundnatrules/inboundnatrules_mock.go b/azure/services/inboundnatrules/mock_inboundnatrules/inboundnatrules_mock.go index 4b0beab7ea5e..374f43162c58 100644 --- a/azure/services/inboundnatrules/mock_inboundnatrules/inboundnatrules_mock.go +++ b/azure/services/inboundnatrules/mock_inboundnatrules/inboundnatrules_mock.go @@ -205,6 +205,48 @@ func (mr *MockInboundNatScopeMockRecorder) DeleteLongRunningOperationState(arg0, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLongRunningOperationState", reflect.TypeOf((*MockInboundNatScope)(nil).DeleteLongRunningOperationState), arg0, arg1, arg2) } +// ExtendedLocation mocks base method. +func (m *MockInboundNatScope) ExtendedLocation() *v1beta1.ExtendedLocationSpec { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocation") + ret0, _ := ret[0].(*v1beta1.ExtendedLocationSpec) + return ret0 +} + +// ExtendedLocation indicates an expected call of ExtendedLocation. +func (mr *MockInboundNatScopeMockRecorder) ExtendedLocation() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocation", reflect.TypeOf((*MockInboundNatScope)(nil).ExtendedLocation)) +} + +// ExtendedLocationName mocks base method. +func (m *MockInboundNatScope) ExtendedLocationName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationName indicates an expected call of ExtendedLocationName. +func (mr *MockInboundNatScopeMockRecorder) ExtendedLocationName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationName", reflect.TypeOf((*MockInboundNatScope)(nil).ExtendedLocationName)) +} + +// ExtendedLocationType mocks base method. +func (m *MockInboundNatScope) ExtendedLocationType() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationType") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationType indicates an expected call of ExtendedLocationType. +func (mr *MockInboundNatScopeMockRecorder) ExtendedLocationType() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationType", reflect.TypeOf((*MockInboundNatScope)(nil).ExtendedLocationType)) +} + // FailureDomains mocks base method. func (m *MockInboundNatScope) FailureDomains() []string { m.ctrl.T.Helper() diff --git a/azure/services/loadbalancers/mock_loadbalancers/loadbalancers_mock.go b/azure/services/loadbalancers/mock_loadbalancers/loadbalancers_mock.go index 23a1d8def024..262de93300d8 100644 --- a/azure/services/loadbalancers/mock_loadbalancers/loadbalancers_mock.go +++ b/azure/services/loadbalancers/mock_loadbalancers/loadbalancers_mock.go @@ -261,6 +261,48 @@ func (mr *MockLBScopeMockRecorder) DeleteLongRunningOperationState(arg0, arg1, a return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLongRunningOperationState", reflect.TypeOf((*MockLBScope)(nil).DeleteLongRunningOperationState), arg0, arg1, arg2) } +// ExtendedLocation mocks base method. +func (m *MockLBScope) ExtendedLocation() *v1beta1.ExtendedLocationSpec { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocation") + ret0, _ := ret[0].(*v1beta1.ExtendedLocationSpec) + return ret0 +} + +// ExtendedLocation indicates an expected call of ExtendedLocation. +func (mr *MockLBScopeMockRecorder) ExtendedLocation() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocation", reflect.TypeOf((*MockLBScope)(nil).ExtendedLocation)) +} + +// ExtendedLocationName mocks base method. +func (m *MockLBScope) ExtendedLocationName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationName indicates an expected call of ExtendedLocationName. +func (mr *MockLBScopeMockRecorder) ExtendedLocationName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationName", reflect.TypeOf((*MockLBScope)(nil).ExtendedLocationName)) +} + +// ExtendedLocationType mocks base method. +func (m *MockLBScope) ExtendedLocationType() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationType") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationType indicates an expected call of ExtendedLocationType. +func (mr *MockLBScopeMockRecorder) ExtendedLocationType() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationType", reflect.TypeOf((*MockLBScope)(nil).ExtendedLocationType)) +} + // FailureDomains mocks base method. func (m *MockLBScope) FailureDomains() []string { m.ctrl.T.Helper() diff --git a/azure/services/loadbalancers/spec.go b/azure/services/loadbalancers/spec.go index b3a391753ca6..5dff099ed729 100644 --- a/azure/services/loadbalancers/spec.go +++ b/azure/services/loadbalancers/spec.go @@ -34,6 +34,7 @@ type LBSpec struct { SubscriptionID string ClusterName string Location string + ExtendedLocation *infrav1.ExtendedLocationSpec Role string Type infrav1.LBType SKU infrav1.SKU @@ -139,9 +140,10 @@ func (s *LBSpec) Parameters(ctx context.Context, existing interface{}) (paramete } lb := network.LoadBalancer{ - Etag: etag, - Sku: &network.LoadBalancerSku{Name: converters.SKUtoSDK(s.SKU)}, - Location: pointer.String(s.Location), + Etag: etag, + Sku: &network.LoadBalancerSku{Name: converters.SKUtoSDK(s.SKU)}, + Location: pointer.String(s.Location), + ExtendedLocation: converters.ExtendedLocationToNetworkSDK(s.ExtendedLocation), Tags: converters.TagsToMap(infrav1.Build(infrav1.BuildParams{ ClusterName: s.ClusterName, Lifecycle: infrav1.ResourceLifecycleOwned, diff --git a/azure/services/natgateways/mock_natgateways/natgateways_mock.go b/azure/services/natgateways/mock_natgateways/natgateways_mock.go index 6abf4a1c543b..4b72a34a12eb 100644 --- a/azure/services/natgateways/mock_natgateways/natgateways_mock.go +++ b/azure/services/natgateways/mock_natgateways/natgateways_mock.go @@ -261,6 +261,48 @@ func (mr *MockNatGatewayScopeMockRecorder) DeleteLongRunningOperationState(arg0, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLongRunningOperationState", reflect.TypeOf((*MockNatGatewayScope)(nil).DeleteLongRunningOperationState), arg0, arg1, arg2) } +// ExtendedLocation mocks base method. +func (m *MockNatGatewayScope) ExtendedLocation() *v1beta1.ExtendedLocationSpec { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocation") + ret0, _ := ret[0].(*v1beta1.ExtendedLocationSpec) + return ret0 +} + +// ExtendedLocation indicates an expected call of ExtendedLocation. +func (mr *MockNatGatewayScopeMockRecorder) ExtendedLocation() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocation", reflect.TypeOf((*MockNatGatewayScope)(nil).ExtendedLocation)) +} + +// ExtendedLocationName mocks base method. +func (m *MockNatGatewayScope) ExtendedLocationName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationName indicates an expected call of ExtendedLocationName. +func (mr *MockNatGatewayScopeMockRecorder) ExtendedLocationName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationName", reflect.TypeOf((*MockNatGatewayScope)(nil).ExtendedLocationName)) +} + +// ExtendedLocationType mocks base method. +func (m *MockNatGatewayScope) ExtendedLocationType() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationType") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationType indicates an expected call of ExtendedLocationType. +func (mr *MockNatGatewayScopeMockRecorder) ExtendedLocationType() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationType", reflect.TypeOf((*MockNatGatewayScope)(nil).ExtendedLocationType)) +} + // FailureDomains mocks base method. func (m *MockNatGatewayScope) FailureDomains() []string { m.ctrl.T.Helper() diff --git a/azure/services/networkinterfaces/mock_networkinterfaces/networkinterfaces_mock.go b/azure/services/networkinterfaces/mock_networkinterfaces/networkinterfaces_mock.go index 22d9e7c41161..779d9e2c329a 100644 --- a/azure/services/networkinterfaces/mock_networkinterfaces/networkinterfaces_mock.go +++ b/azure/services/networkinterfaces/mock_networkinterfaces/networkinterfaces_mock.go @@ -191,6 +191,48 @@ func (mr *MockNICScopeMockRecorder) DeleteLongRunningOperationState(arg0, arg1, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLongRunningOperationState", reflect.TypeOf((*MockNICScope)(nil).DeleteLongRunningOperationState), arg0, arg1, arg2) } +// ExtendedLocation mocks base method. +func (m *MockNICScope) ExtendedLocation() *v1beta1.ExtendedLocationSpec { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocation") + ret0, _ := ret[0].(*v1beta1.ExtendedLocationSpec) + return ret0 +} + +// ExtendedLocation indicates an expected call of ExtendedLocation. +func (mr *MockNICScopeMockRecorder) ExtendedLocation() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocation", reflect.TypeOf((*MockNICScope)(nil).ExtendedLocation)) +} + +// ExtendedLocationName mocks base method. +func (m *MockNICScope) ExtendedLocationName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationName indicates an expected call of ExtendedLocationName. +func (mr *MockNICScopeMockRecorder) ExtendedLocationName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationName", reflect.TypeOf((*MockNICScope)(nil).ExtendedLocationName)) +} + +// ExtendedLocationType mocks base method. +func (m *MockNICScope) ExtendedLocationType() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationType") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationType indicates an expected call of ExtendedLocationType. +func (mr *MockNICScopeMockRecorder) ExtendedLocationType() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationType", reflect.TypeOf((*MockNICScope)(nil).ExtendedLocationType)) +} + // FailureDomains mocks base method. func (m *MockNICScope) FailureDomains() []string { m.ctrl.T.Helper() diff --git a/azure/services/networkinterfaces/spec.go b/azure/services/networkinterfaces/spec.go index 7b79e3734b61..65fabdea65b2 100644 --- a/azure/services/networkinterfaces/spec.go +++ b/azure/services/networkinterfaces/spec.go @@ -34,6 +34,7 @@ type NICSpec struct { Name string ResourceGroup string Location string + ExtendedLocation *infrav1.ExtendedLocationSpec SubscriptionID string MachineName string SubnetName string @@ -201,7 +202,8 @@ func (s *NICSpec) Parameters(ctx context.Context, existing interface{}) (paramet } return network.Interface{ - Location: pointer.String(s.Location), + Location: pointer.String(s.Location), + ExtendedLocation: converters.ExtendedLocationToNetworkSDK(s.ExtendedLocation), InterfacePropertiesFormat: &network.InterfacePropertiesFormat{ EnableAcceleratedNetworking: s.AcceleratedNetworking, IPConfigurations: &ipConfigurations, diff --git a/azure/services/privatedns/mock_privatedns/privatedns_mock.go b/azure/services/privatedns/mock_privatedns/privatedns_mock.go index a05960f49d06..13e5a3fe3a67 100644 --- a/azure/services/privatedns/mock_privatedns/privatedns_mock.go +++ b/azure/services/privatedns/mock_privatedns/privatedns_mock.go @@ -191,6 +191,48 @@ func (mr *MockScopeMockRecorder) DeleteLongRunningOperationState(arg0, arg1, arg return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLongRunningOperationState", reflect.TypeOf((*MockScope)(nil).DeleteLongRunningOperationState), arg0, arg1, arg2) } +// ExtendedLocation mocks base method. +func (m *MockScope) ExtendedLocation() *v1beta1.ExtendedLocationSpec { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocation") + ret0, _ := ret[0].(*v1beta1.ExtendedLocationSpec) + return ret0 +} + +// ExtendedLocation indicates an expected call of ExtendedLocation. +func (mr *MockScopeMockRecorder) ExtendedLocation() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocation", reflect.TypeOf((*MockScope)(nil).ExtendedLocation)) +} + +// ExtendedLocationName mocks base method. +func (m *MockScope) ExtendedLocationName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationName indicates an expected call of ExtendedLocationName. +func (mr *MockScopeMockRecorder) ExtendedLocationName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationName", reflect.TypeOf((*MockScope)(nil).ExtendedLocationName)) +} + +// ExtendedLocationType mocks base method. +func (m *MockScope) ExtendedLocationType() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationType") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationType indicates an expected call of ExtendedLocationType. +func (mr *MockScopeMockRecorder) ExtendedLocationType() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationType", reflect.TypeOf((*MockScope)(nil).ExtendedLocationType)) +} + // FailureDomains mocks base method. func (m *MockScope) FailureDomains() []string { m.ctrl.T.Helper() diff --git a/azure/services/publicips/mock_publicips/publicips_mock.go b/azure/services/publicips/mock_publicips/publicips_mock.go index 854841e01ca6..d80ced848753 100644 --- a/azure/services/publicips/mock_publicips/publicips_mock.go +++ b/azure/services/publicips/mock_publicips/publicips_mock.go @@ -191,6 +191,48 @@ func (mr *MockPublicIPScopeMockRecorder) DeleteLongRunningOperationState(arg0, a return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLongRunningOperationState", reflect.TypeOf((*MockPublicIPScope)(nil).DeleteLongRunningOperationState), arg0, arg1, arg2) } +// ExtendedLocation mocks base method. +func (m *MockPublicIPScope) ExtendedLocation() *v1beta1.ExtendedLocationSpec { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocation") + ret0, _ := ret[0].(*v1beta1.ExtendedLocationSpec) + return ret0 +} + +// ExtendedLocation indicates an expected call of ExtendedLocation. +func (mr *MockPublicIPScopeMockRecorder) ExtendedLocation() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocation", reflect.TypeOf((*MockPublicIPScope)(nil).ExtendedLocation)) +} + +// ExtendedLocationName mocks base method. +func (m *MockPublicIPScope) ExtendedLocationName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationName indicates an expected call of ExtendedLocationName. +func (mr *MockPublicIPScopeMockRecorder) ExtendedLocationName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationName", reflect.TypeOf((*MockPublicIPScope)(nil).ExtendedLocationName)) +} + +// ExtendedLocationType mocks base method. +func (m *MockPublicIPScope) ExtendedLocationType() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationType") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationType indicates an expected call of ExtendedLocationType. +func (mr *MockPublicIPScopeMockRecorder) ExtendedLocationType() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationType", reflect.TypeOf((*MockPublicIPScope)(nil).ExtendedLocationType)) +} + // FailureDomains mocks base method. func (m *MockPublicIPScope) FailureDomains() []string { m.ctrl.T.Helper() diff --git a/azure/services/publicips/spec.go b/azure/services/publicips/spec.go index 8f48f6c9fb49..ce715128e09e 100644 --- a/azure/services/publicips/spec.go +++ b/azure/services/publicips/spec.go @@ -29,15 +29,16 @@ import ( // PublicIPSpec defines the specification for a Public IP. type PublicIPSpec struct { - Name string - ResourceGroup string - ClusterName string - DNSName string - IsIPv6 bool - Location string - FailureDomains []string - AdditionalTags infrav1.Tags - IPTags []infrav1.IPTag + Name string + ResourceGroup string + ClusterName string + DNSName string + IsIPv6 bool + Location string + ExtendedLocation *infrav1.ExtendedLocationSpec + FailureDomains []string + AdditionalTags infrav1.Tags + IPTags []infrav1.IPTag } // ResourceName returns the name of the public IP. @@ -86,9 +87,10 @@ func (s *PublicIPSpec) Parameters(ctx context.Context, existing interface{}) (pa Name: pointer.String(s.Name), Additional: s.AdditionalTags, })), - Sku: &network.PublicIPAddressSku{Name: network.PublicIPAddressSkuNameStandard}, - Name: pointer.String(s.Name), - Location: pointer.String(s.Location), + Sku: &network.PublicIPAddressSku{Name: network.PublicIPAddressSkuNameStandard}, + Name: pointer.String(s.Name), + Location: pointer.String(s.Location), + ExtendedLocation: converters.ExtendedLocationToNetworkSDK(s.ExtendedLocation), PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{ PublicIPAddressVersion: addressVersion, PublicIPAllocationMethod: network.IPAllocationMethodStatic, diff --git a/azure/services/scalesets/mock_scalesets/scalesets_mock.go b/azure/services/scalesets/mock_scalesets/scalesets_mock.go index 88c8d86bcce0..930a8618cb39 100644 --- a/azure/services/scalesets/mock_scalesets/scalesets_mock.go +++ b/azure/services/scalesets/mock_scalesets/scalesets_mock.go @@ -192,6 +192,48 @@ func (mr *MockScaleSetScopeMockRecorder) DeleteLongRunningOperationState(arg0, a return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLongRunningOperationState", reflect.TypeOf((*MockScaleSetScope)(nil).DeleteLongRunningOperationState), arg0, arg1, arg2) } +// ExtendedLocation mocks base method. +func (m *MockScaleSetScope) ExtendedLocation() *v1beta1.ExtendedLocationSpec { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocation") + ret0, _ := ret[0].(*v1beta1.ExtendedLocationSpec) + return ret0 +} + +// ExtendedLocation indicates an expected call of ExtendedLocation. +func (mr *MockScaleSetScopeMockRecorder) ExtendedLocation() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocation", reflect.TypeOf((*MockScaleSetScope)(nil).ExtendedLocation)) +} + +// ExtendedLocationName mocks base method. +func (m *MockScaleSetScope) ExtendedLocationName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationName indicates an expected call of ExtendedLocationName. +func (mr *MockScaleSetScopeMockRecorder) ExtendedLocationName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationName", reflect.TypeOf((*MockScaleSetScope)(nil).ExtendedLocationName)) +} + +// ExtendedLocationType mocks base method. +func (m *MockScaleSetScope) ExtendedLocationType() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationType") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationType indicates an expected call of ExtendedLocationType. +func (mr *MockScaleSetScopeMockRecorder) ExtendedLocationType() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationType", reflect.TypeOf((*MockScaleSetScope)(nil).ExtendedLocationType)) +} + // FailureDomains mocks base method. func (m *MockScaleSetScope) FailureDomains() []string { m.ctrl.T.Helper() diff --git a/azure/services/scalesetvms/mock_scalesetvms/scalesetvms_mock.go b/azure/services/scalesetvms/mock_scalesetvms/scalesetvms_mock.go index d7af6996ae40..787554d67c53 100644 --- a/azure/services/scalesetvms/mock_scalesetvms/scalesetvms_mock.go +++ b/azure/services/scalesetvms/mock_scalesetvms/scalesetvms_mock.go @@ -191,6 +191,48 @@ func (mr *MockScaleSetVMScopeMockRecorder) DeleteLongRunningOperationState(arg0, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLongRunningOperationState", reflect.TypeOf((*MockScaleSetVMScope)(nil).DeleteLongRunningOperationState), arg0, arg1, arg2) } +// ExtendedLocation mocks base method. +func (m *MockScaleSetVMScope) ExtendedLocation() *v1beta1.ExtendedLocationSpec { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocation") + ret0, _ := ret[0].(*v1beta1.ExtendedLocationSpec) + return ret0 +} + +// ExtendedLocation indicates an expected call of ExtendedLocation. +func (mr *MockScaleSetVMScopeMockRecorder) ExtendedLocation() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocation", reflect.TypeOf((*MockScaleSetVMScope)(nil).ExtendedLocation)) +} + +// ExtendedLocationName mocks base method. +func (m *MockScaleSetVMScope) ExtendedLocationName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationName indicates an expected call of ExtendedLocationName. +func (mr *MockScaleSetVMScopeMockRecorder) ExtendedLocationName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationName", reflect.TypeOf((*MockScaleSetVMScope)(nil).ExtendedLocationName)) +} + +// ExtendedLocationType mocks base method. +func (m *MockScaleSetVMScope) ExtendedLocationType() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtendedLocationType") + ret0, _ := ret[0].(string) + return ret0 +} + +// ExtendedLocationType indicates an expected call of ExtendedLocationType. +func (mr *MockScaleSetVMScopeMockRecorder) ExtendedLocationType() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendedLocationType", reflect.TypeOf((*MockScaleSetVMScope)(nil).ExtendedLocationType)) +} + // FailureDomains mocks base method. func (m *MockScaleSetVMScope) FailureDomains() []string { m.ctrl.T.Helper() diff --git a/azure/services/virtualmachines/spec.go b/azure/services/virtualmachines/spec.go index c6d7fc83e9b4..673e7844fd9c 100644 --- a/azure/services/virtualmachines/spec.go +++ b/azure/services/virtualmachines/spec.go @@ -36,6 +36,7 @@ type VMSpec struct { Name string ResourceGroup string Location string + ExtendedLocation *infrav1.ExtendedLocationSpec ClusterName string Role string NICIDs []string @@ -114,8 +115,9 @@ func (s *VMSpec) Parameters(ctx context.Context, existing interface{}) (params i } return compute.VirtualMachine{ - Plan: converters.ImageToPlan(s.Image), - Location: pointer.String(s.Location), + Plan: converters.ImageToPlan(s.Image), + Location: pointer.String(s.Location), + ExtendedLocation: converters.ExtendedLocationToComputeSDK(s.ExtendedLocation), Tags: converters.TagsToMap(infrav1.Build(infrav1.BuildParams{ ClusterName: s.ClusterName, Lifecycle: infrav1.ResourceLifecycleOwned, diff --git a/azure/services/virtualnetworks/spec.go b/azure/services/virtualnetworks/spec.go index 6159ff0548d7..08c702119018 100644 --- a/azure/services/virtualnetworks/spec.go +++ b/azure/services/virtualnetworks/spec.go @@ -27,12 +27,13 @@ import ( // VNetSpec defines the specification for a Virtual Network. type VNetSpec struct { - ResourceGroup string - Name string - CIDRs []string - Location string - ClusterName string - AdditionalTags infrav1.Tags + ResourceGroup string + Name string + CIDRs []string + Location string + ExtendedLocation *infrav1.ExtendedLocationSpec + ClusterName string + AdditionalTags infrav1.Tags } // ResourceName returns the name of the vnet. @@ -64,7 +65,8 @@ func (s *VNetSpec) Parameters(ctx context.Context, existing interface{}) (interf Role: pointer.String(infrav1.CommonRole), Additional: s.AdditionalTags, })), - Location: pointer.String(s.Location), + Location: pointer.String(s.Location), + ExtendedLocation: converters.ExtendedLocationToNetworkSDK(s.ExtendedLocation), VirtualNetworkPropertiesFormat: &network.VirtualNetworkPropertiesFormat{ AddressSpace: &network.AddressSpace{ AddressPrefixes: &s.CIDRs, diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclusters.yaml index 39435d581352..041665518c92 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclusters.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclusters.yaml @@ -1756,6 +1756,22 @@ spec: - host - port type: object + extendedLocation: + description: ExtendedLocation is an optional set of ExtendedLocation + properties for clusters on Azure public MEC. + properties: + name: + description: Name defines the name for the extended location. + type: string + type: + description: Type defines the type for the extended location. + enum: + - EdgeZone + type: string + required: + - name + - type + type: object identityRef: description: IdentityRef is a reference to an AzureIdentity to be used when reconciling this cluster diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclustertemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclustertemplates.yaml index 3a50bf7e2fa8..850314b6c7ea 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclustertemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclustertemplates.yaml @@ -406,6 +406,22 @@ spec: type: object type: array type: object + extendedLocation: + description: ExtendedLocation is an optional set of ExtendedLocation + properties for clusters on Azure public MEC. + properties: + name: + description: Name defines the name for the extended location. + type: string + type: + description: Type defines the type for the extended location. + enum: + - EdgeZone + type: string + required: + - name + - type + type: object identityRef: description: IdentityRef is a reference to an AzureIdentity to be used when reconciling this cluster diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 79108988f258..98b04bd30b0d 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -23,7 +23,7 @@ spec: - args: - --leader-elect - "--metrics-bind-addr=localhost:8080" - - "--feature-gates=MachinePool=${EXP_MACHINE_POOL:=false},AKSResourceHealth=${EXP_AKS_RESOURCE_HEALTH:=false}" + - "--feature-gates=MachinePool=${EXP_MACHINE_POOL:=false},AKSResourceHealth=${EXP_AKS_RESOURCE_HEALTH:=false},EdgeZone=${EXP_EDGEZONE:=false}" - "--v=0" image: controller:latest imagePullPolicy: Always diff --git a/controllers/azurecluster_reconciler.go b/controllers/azurecluster_reconciler.go index 33cae1c1765b..a4e3787978c4 100644 --- a/controllers/azurecluster_reconciler.go +++ b/controllers/azurecluster_reconciler.go @@ -154,6 +154,10 @@ func (s *azureClusterService) getService(name string) (azure.ServiceReconciler, // setFailureDomainsForLocation sets the AzureCluster Status failure domains based on which Azure Availability Zones are available in the cluster location. // Note that this is not done in a webhook as it requires API calls to fetch the availability zones. func (s *azureClusterService) setFailureDomainsForLocation(ctx context.Context) error { + if s.scope.ExtendedLocation() != nil { + return nil + } + zones, err := s.skuCache.GetZones(ctx, s.scope.Location()) if err != nil { return errors.Wrapf(err, "failed to get zones for location %s", s.scope.Location()) diff --git a/controllers/helpers.go b/controllers/helpers.go index 3c58bd26286a..7a64155c72d5 100644 --- a/controllers/helpers.go +++ b/controllers/helpers.go @@ -266,6 +266,8 @@ func newCloudProviderConfig(d azure.ClusterScoper) (controlPlaneConfig *CloudPro SecurityGroupName: subnet.SecurityGroup.Name, SecurityGroupResourceGroup: d.Vnet().ResourceGroup, Location: d.Location(), + ExtendedLocationType: d.ExtendedLocationType(), + ExtendedLocationName: d.ExtendedLocationName(), VMType: "vmss", VnetName: d.Vnet().Name, VnetResourceGroup: d.Vnet().ResourceGroup, @@ -287,6 +289,8 @@ func newCloudProviderConfig(d azure.ClusterScoper) (controlPlaneConfig *CloudPro SecurityGroupName: subnet.SecurityGroup.Name, SecurityGroupResourceGroup: d.Vnet().ResourceGroup, Location: d.Location(), + ExtendedLocationType: d.ExtendedLocationType(), + ExtendedLocationName: d.ExtendedLocationName(), VMType: "vmss", VnetName: d.Vnet().Name, VnetResourceGroup: d.Vnet().ResourceGroup, @@ -321,6 +325,8 @@ type CloudProviderConfig struct { SecurityGroupName string `json:"securityGroupName"` SecurityGroupResourceGroup string `json:"securityGroupResourceGroup"` Location string `json:"location"` + ExtendedLocationType string `json:"extendedLocationType,omitempty"` + ExtendedLocationName string `json:"extendedLocationName,omitempty"` VMType string `json:"vmType"` VnetName string `json:"vnetName"` VnetResourceGroup string `json:"vnetResourceGroup"` diff --git a/docs/book/src/topics/publicmec-clusters.md b/docs/book/src/topics/publicmec-clusters.md new file mode 100644 index 000000000000..7ff340d3dfdf --- /dev/null +++ b/docs/book/src/topics/publicmec-clusters.md @@ -0,0 +1,84 @@ +# Deploy cluster on Public MEC + +- **Feature status:** Experimental +- **Feature gate:** EdgeZone=true + +## Overview + +Cluster API Provider Azure (CAPZ) has experimental support for deploying clusters on [Azure Public MEC](https://azure.microsoft.com/en-us/solutions/public-multi-access-edge-compute-mec). Before you begin, you need an Azure subscription which has access to Public MEC. + +To deploy a cluster on Public MEC, provide extended location info through environment variables and use the "edgezone" flavor. + +## Example: Deploy cluster on Public MEC by `clusterctl` + +The clusterctl "edgezone" flavor exists to deploy clusters on Public MEC. This flavor requires the following environment variables to be set before executing `clusterctl`. + +```bash +# Kubernetes values +export CLUSTER_NAME="my-cluster" +export WORKER_MACHINE_COUNT=2 +export CONTROL_PLANE_MACHINE_COUNT=1 +export KUBERNETES_VERSION="v1.25.0" + +# Azure values +export AZURE_LOCATION="eastus2euap" +export AZURE_EXTENDEDLOCATION_TYPE="EdgeZone" +export AZURE_EXTENDEDLOCATION_NAME="microsoftrrdclab3" +export AZURE_RESOURCE_GROUP="${CLUSTER_NAME}" +``` + +Create a new service principal and save to local file: +```bash +az ad sp create-for-rbac --role Contributor --scopes="/subscriptions/${AZURE_SUBSCRIPTION_ID}" --sdk-auth > sp.json +``` +Export the following variables to your current shell: +```bash +export AZURE_SUBSCRIPTION_ID="$(cat sp.json | jq -r .subscriptionId | tr -d '\n')" +export AZURE_CLIENT_SECRET="$(cat sp.json | jq -r .clientSecret | tr -d '\n')" +export AZURE_CLIENT_ID="$(cat sp.json | jq -r .clientId | tr -d '\n')" +export AZURE_CONTROL_PLANE_MACHINE_TYPE="Standard_D2s_v3" +export AZURE_NODE_MACHINE_TYPE="Standard_D2s_v3" +export AZURE_CLUSTER_IDENTITY_SECRET_NAME="cluster-identity-secret" +export AZURE_CLUSTER_IDENTITY_SECRET_NAMESPACE="default" +export CLUSTER_IDENTITY_NAME="cluster-identity" +``` + +Public MEC-enabled clusters also require the following feature flags set as environment variables: + +```bash +export EXP_EDGEZONE=true +``` + +Create a local kind cluster to run the management cluster components: + +```bash +kind create cluster +``` + +Create an identity secret on the management cluster: + +```bash +kubectl create secret generic "${AZURE_CLUSTER_IDENTITY_SECRET_NAME}" --from-literal=clientSecret="${AZURE_CLIENT_SECRET}" +``` + +Execute clusterctl to template the resources: + +```bash +clusterctl init --infrastructure azure +clusterctl generate cluster ${CLUSTER_NAME} --kubernetes-version ${KUBERNETES_VERSION} --flavor edgezone > edgezone-cluster.yaml +``` +Public MEC doesn't have access to CAPI images in Azure Marketplace, therefore, users need to prepare CAPI image by themselves. You can follow doc [Custom Images](https://capz.sigs.k8s.io/topics/custom-images.html) to setup custom image. + +Apply the modifed template to your kind management cluster: +```bash +kubectl apply -f edgezone-cluster.yaml +``` + +Once target cluster's control plane is up, install [Azure cloud provider components](https://github.com/kubernetes-sigs/cloud-provider-azure/tree/master/helm/cloud-provider-azure) by helm. The minimum version for "out-of-tree" Azure cloud provider is v1.0.3, "in-tree" Azure cloud provider is not supported. (Reference: https://capz.sigs.k8s.io/topics/addons.html#external-cloud-provider) + +```bash +# get the kubeconfig of the cluster +kubectl get secrets ${CLUSTER_NAME}-kubeconfig -o json | jq -r .data.value | base64 --decode > ./kubeconfig + +helm install --repo https://raw.githubusercontent.com/kubernetes-sigs/cloud-provider-azure/master/helm/repo cloud-provider-azure --generate-name --set infra.clusterName=${CLUSTER_NAME} --kubeconfig=./kubeconfig +``` \ No newline at end of file diff --git a/feature/feature.go b/feature/feature.go index 033cf959004d..e3e3d7b81aa0 100644 --- a/feature/feature.go +++ b/feature/feature.go @@ -42,6 +42,11 @@ const ( // owner: @nojnhuh // alpha: v1.7 AKSResourceHealth featuregate.Feature = "AKSResourceHealth" + + // EdgeZone is the feature gate for creating clusters on public MEC. + // owner: @upxinxin + // alpha: v1.8 + EdgeZone featuregate.Feature = "EdgeZone" ) func init() { @@ -54,4 +59,5 @@ var defaultCAPZFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ // Every feature should be initiated here: AKS: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // Remove in 1.12 AKSResourceHealth: {Default: false, PreRelease: featuregate.Alpha}, + EdgeZone: {Default: false, PreRelease: featuregate.Alpha}, } diff --git a/go.mod b/go.mod index 3a4916c8b80f..c868af4af424 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.1 github.com/Azure/go-autorest/autorest v0.11.28 github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 + github.com/Azure/go-autorest/autorest/to v0.4.0 github.com/Azure/go-autorest/tracing v0.6.0 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/blang/semver v3.5.1+incompatible @@ -60,7 +61,6 @@ require ( github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/autorest/mocks v0.4.2 // indirect - github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1 // indirect diff --git a/hack/observability/opentelemetry/controller-manager-patch.yaml b/hack/observability/opentelemetry/controller-manager-patch.yaml index 9bd489509149..f1b55a41e406 100644 --- a/hack/observability/opentelemetry/controller-manager-patch.yaml +++ b/hack/observability/opentelemetry/controller-manager-patch.yaml @@ -11,5 +11,5 @@ spec: args: - "--metrics-bind-addr=:8080" - "--leader-elect" - - "--feature-gates=MachinePool=${EXP_MACHINE_POOL:=false},AKSResourceHealth=${EXP_AKS_RESOURCE_HEALTH:=false}" + - "--feature-gates=MachinePool=${EXP_MACHINE_POOL:=false},AKSResourceHealth=${EXP_AKS_RESOURCE_HEALTH:=false},EdgeZone=${EXP_EDGEZONE:=false}" - "--enable-tracing" diff --git a/hack/util.sh b/hack/util.sh index 529dbffd7888..6039db92cf2e 100755 --- a/hack/util.sh +++ b/hack/util.sh @@ -51,6 +51,11 @@ capz::util::get_random_region_gpu() { local REGIONS=("eastus" "eastus2" "northeurope" "uksouth" "westeurope" "westus2") echo "${REGIONS[${RANDOM} % ${#REGIONS[@]}]}" } +# all regions below must support ExtendedLocation +capz::util::get_random_region_edgezone() { + local REGIONS=("canadacentral") + echo "${REGIONS[${RANDOM} % ${#REGIONS[@]}]}" +} capz::util::generate_ssh_key() { # Generate SSH key. diff --git a/scripts/ci-conformance.sh b/scripts/ci-conformance.sh index f2a6f2fbcc95..464c716232f2 100755 --- a/scripts/ci-conformance.sh +++ b/scripts/ci-conformance.sh @@ -63,6 +63,7 @@ export GINKGO_NODES=1 export AZURE_LOCATION="${AZURE_LOCATION:-$(capz::util::get_random_region)}" export AZURE_LOCATION_GPU="${AZURE_LOCATION_GPU:-$(capz::util::get_random_region_gpu)}" +export AZURE_LOCATION_EDGEZONE="${AZURE_LOCATION_EDGEZONE:-$(capz::util::get_random_region_edgezone)}" export AZURE_CONTROL_PLANE_MACHINE_TYPE="${AZURE_CONTROL_PLANE_MACHINE_TYPE:-"Standard_B2s"}" export AZURE_NODE_MACHINE_TYPE="${AZURE_NODE_MACHINE_TYPE:-"Standard_B2s"}" export WINDOWS="${WINDOWS:-false}" diff --git a/scripts/ci-e2e.sh b/scripts/ci-e2e.sh index 5c74e59259c8..c35a670dc744 100755 --- a/scripts/ci-e2e.sh +++ b/scripts/ci-e2e.sh @@ -61,6 +61,7 @@ export GINKGO_NODES=10 export AZURE_LOCATION="${AZURE_LOCATION:-$(capz::util::get_random_region)}" export AZURE_LOCATION_GPU="${AZURE_LOCATION_GPU:-$(capz::util::get_random_region_gpu)}" +export AZURE_LOCATION_EDGEZONE="${AZURE_LOCATION_EDGEZONE:-$(capz::util::get_random_region_edgezone)}" export AZURE_CONTROL_PLANE_MACHINE_TYPE="${AZURE_CONTROL_PLANE_MACHINE_TYPE:-"Standard_B2s"}" export AZURE_NODE_MACHINE_TYPE="${AZURE_NODE_MACHINE_TYPE:-"Standard_B2s"}" export KIND_EXPERIMENTAL_DOCKER_NETWORK="bridge" diff --git a/scripts/ci-entrypoint.sh b/scripts/ci-entrypoint.sh index 1369d50b45eb..61427d609a32 100755 --- a/scripts/ci-entrypoint.sh +++ b/scripts/ci-entrypoint.sh @@ -86,6 +86,8 @@ setup() { echo "Using AZURE_LOCATION: ${AZURE_LOCATION}" export AZURE_LOCATION_GPU="${AZURE_LOCATION_GPU:-$(capz::util::get_random_region_gpu)}" echo "Using AZURE_LOCATION_GPU: ${AZURE_LOCATION_GPU}" + export AZURE_LOCATION_EDGEZONE="${AZURE_LOCATION_EDGEZONE:-$(capz::util::get_random_region_edgezone)}" + echo "Using AZURE_LOCATION_EDGEZONE: ${AZURE_LOCATION_EDGEZONE}" # Need a cluster with at least 2 nodes export CONTROL_PLANE_MACHINE_COUNT="${CONTROL_PLANE_MACHINE_COUNT:-1}" export CCM_COUNT="${CCM_COUNT:-1}" diff --git a/templates/cluster-template-edgezone.yaml b/templates/cluster-template-edgezone.yaml new file mode 100644 index 000000000000..230733a1fade --- /dev/null +++ b/templates/cluster-template-edgezone.yaml @@ -0,0 +1,231 @@ +apiVersion: cluster.x-k8s.io/v1beta1 +kind: Cluster +metadata: + name: ${CLUSTER_NAME} + namespace: default +spec: + clusterNetwork: + pods: + cidrBlocks: + - 192.168.0.0/16 + controlPlaneRef: + apiVersion: controlplane.cluster.x-k8s.io/v1beta1 + kind: KubeadmControlPlane + name: ${CLUSTER_NAME}-control-plane + infrastructureRef: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: AzureCluster + name: ${CLUSTER_NAME} +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: AzureCluster +metadata: + name: ${CLUSTER_NAME} + namespace: default +spec: + extendedLocation: + name: ${AZURE_EXTENDEDLOCATION_NAME} + type: ${AZURE_EXTENDEDLOCATION_TYPE} + identityRef: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: AzureClusterIdentity + name: ${CLUSTER_IDENTITY_NAME} + location: ${AZURE_LOCATION} + networkSpec: + subnets: + - name: control-plane-subnet + role: control-plane + - name: node-subnet + role: node + vnet: + name: ${AZURE_VNET_NAME:=${CLUSTER_NAME}-vnet} + resourceGroup: ${AZURE_RESOURCE_GROUP:=${CLUSTER_NAME}} + subscriptionID: ${AZURE_SUBSCRIPTION_ID} +--- +apiVersion: controlplane.cluster.x-k8s.io/v1beta1 +kind: KubeadmControlPlane +metadata: + name: ${CLUSTER_NAME}-control-plane + namespace: default +spec: + kubeadmConfigSpec: + clusterConfiguration: + apiServer: + extraArgs: + cloud-config: /etc/kubernetes/azure.json + cloud-provider: external + extraVolumes: + - hostPath: /etc/kubernetes/azure.json + mountPath: /etc/kubernetes/azure.json + name: cloud-config + readOnly: true + timeoutForControlPlane: 20m + controllerManager: + extraArgs: + allocate-node-cidrs: "false" + cloud-config: /etc/kubernetes/azure.json + cloud-provider: external + cluster-name: ${CLUSTER_NAME} + external-cloud-volume-plugin: azure + feature-gates: CSIMigrationAzureDisk=true + extraVolumes: + - hostPath: /etc/kubernetes/azure.json + mountPath: /etc/kubernetes/azure.json + name: cloud-config + readOnly: true + etcd: + local: + dataDir: /var/lib/etcddisk/etcd + extraArgs: + quota-backend-bytes: "8589934592" + diskSetup: + filesystems: + - device: /dev/disk/azure/scsi1/lun0 + extraOpts: + - -E + - lazy_itable_init=1,lazy_journal_init=1 + filesystem: ext4 + label: etcd_disk + - device: ephemeral0.1 + filesystem: ext4 + label: ephemeral0 + replaceFS: ntfs + partitions: + - device: /dev/disk/azure/scsi1/lun0 + layout: true + overwrite: false + tableType: gpt + files: + - contentFrom: + secret: + key: control-plane-azure.json + name: ${CLUSTER_NAME}-control-plane-azure-json + owner: root:root + path: /etc/kubernetes/azure.json + permissions: "0644" + initConfiguration: + nodeRegistration: + kubeletExtraArgs: + azure-container-registry-config: /etc/kubernetes/azure.json + cloud-config: /etc/kubernetes/azure.json + cloud-provider: external + feature-gates: CSIMigrationAzureDisk=true + name: '{{ ds.meta_data["local_hostname"] }}' + joinConfiguration: + nodeRegistration: + kubeletExtraArgs: + azure-container-registry-config: /etc/kubernetes/azure.json + cloud-config: /etc/kubernetes/azure.json + cloud-provider: external + feature-gates: CSIMigrationAzureDisk=true + name: '{{ ds.meta_data["local_hostname"] }}' + mounts: + - - LABEL=etcd_disk + - /var/lib/etcddisk + postKubeadmCommands: [] + preKubeadmCommands: [] + machineTemplate: + infrastructureRef: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: AzureMachineTemplate + name: ${CLUSTER_NAME}-control-plane + replicas: ${CONTROL_PLANE_MACHINE_COUNT} + version: ${KUBERNETES_VERSION} +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: AzureMachineTemplate +metadata: + name: ${CLUSTER_NAME}-control-plane + namespace: default +spec: + template: + spec: + dataDisks: + - diskSizeGB: 256 + lun: 0 + nameSuffix: etcddisk + osDisk: + diskSizeGB: 128 + osType: Linux + sshPublicKey: ${AZURE_SSH_PUBLIC_KEY_B64:=""} + vmSize: ${AZURE_CONTROL_PLANE_MACHINE_TYPE} +--- +apiVersion: cluster.x-k8s.io/v1beta1 +kind: MachineDeployment +metadata: + name: ${CLUSTER_NAME}-md-0 + namespace: default +spec: + clusterName: ${CLUSTER_NAME} + replicas: ${WORKER_MACHINE_COUNT} + selector: + matchLabels: null + template: + spec: + bootstrap: + configRef: + apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 + kind: KubeadmConfigTemplate + name: ${CLUSTER_NAME}-md-0 + clusterName: ${CLUSTER_NAME} + infrastructureRef: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: AzureMachineTemplate + name: ${CLUSTER_NAME}-md-0 + version: ${KUBERNETES_VERSION} +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: AzureMachineTemplate +metadata: + name: ${CLUSTER_NAME}-md-0 + namespace: default +spec: + template: + spec: + osDisk: + diskSizeGB: 128 + osType: Linux + sshPublicKey: ${AZURE_SSH_PUBLIC_KEY_B64:=""} + vmSize: ${AZURE_NODE_MACHINE_TYPE} +--- +apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 +kind: KubeadmConfigTemplate +metadata: + name: ${CLUSTER_NAME}-md-0 + namespace: default +spec: + template: + spec: + files: + - contentFrom: + secret: + key: worker-node-azure.json + name: ${CLUSTER_NAME}-md-0-azure-json + owner: root:root + path: /etc/kubernetes/azure.json + permissions: "0644" + joinConfiguration: + nodeRegistration: + kubeletExtraArgs: + azure-container-registry-config: /etc/kubernetes/azure.json + cloud-config: /etc/kubernetes/azure.json + cloud-provider: external + feature-gates: CSIMigrationAzureDisk=true + name: '{{ ds.meta_data["local_hostname"] }}' + preKubeadmCommands: [] +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: AzureClusterIdentity +metadata: + labels: + clusterctl.cluster.x-k8s.io/move-hierarchy: "true" + name: ${CLUSTER_IDENTITY_NAME} + namespace: default +spec: + allowedNamespaces: {} + clientID: ${AZURE_CLIENT_ID} + clientSecret: + name: ${AZURE_CLUSTER_IDENTITY_SECRET_NAME} + namespace: ${AZURE_CLUSTER_IDENTITY_SECRET_NAMESPACE} + tenantID: ${AZURE_TENANT_ID} + type: ServicePrincipal diff --git a/templates/flavors/edgezone/kustomization.yaml b/templates/flavors/edgezone/kustomization.yaml new file mode 100644 index 000000000000..b8592cf5409f --- /dev/null +++ b/templates/flavors/edgezone/kustomization.yaml @@ -0,0 +1,8 @@ +namespace: default +resources: + - ../default + +patchesStrategicMerge: + - ../external-cloud-provider/patches/external-cloud-provider.yaml + - patches/azure-extendedlocation.yaml + - patches/azure-remove-natgateway.yaml diff --git a/templates/flavors/edgezone/patches/azure-extendedlocation.yaml b/templates/flavors/edgezone/patches/azure-extendedlocation.yaml new file mode 100644 index 000000000000..5835ad9ac113 --- /dev/null +++ b/templates/flavors/edgezone/patches/azure-extendedlocation.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: AzureCluster +metadata: + name: ${CLUSTER_NAME} +spec: + extendedLocation: + name: ${AZURE_EXTENDEDLOCATION_NAME} + type: ${AZURE_EXTENDEDLOCATION_TYPE} \ No newline at end of file diff --git a/templates/flavors/edgezone/patches/azure-remove-natgateway.yaml b/templates/flavors/edgezone/patches/azure-remove-natgateway.yaml new file mode 100644 index 000000000000..1ec995e5b13f --- /dev/null +++ b/templates/flavors/edgezone/patches/azure-remove-natgateway.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: AzureCluster +metadata: + name: ${CLUSTER_NAME} +spec: + networkSpec: + subnets: + - name: control-plane-subnet + role: control-plane + - name: node-subnet + role: node \ No newline at end of file diff --git a/templates/test/ci/cluster-template-prow-edgezone.yaml b/templates/test/ci/cluster-template-prow-edgezone.yaml new file mode 100644 index 000000000000..0143e06ea21a --- /dev/null +++ b/templates/test/ci/cluster-template-prow-edgezone.yaml @@ -0,0 +1,253 @@ +apiVersion: cluster.x-k8s.io/v1beta1 +kind: Cluster +metadata: + name: ${CLUSTER_NAME} + namespace: default +spec: + clusterNetwork: + pods: + cidrBlocks: + - 192.168.0.0/16 + controlPlaneRef: + apiVersion: controlplane.cluster.x-k8s.io/v1beta1 + kind: KubeadmControlPlane + name: ${CLUSTER_NAME}-control-plane + infrastructureRef: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: AzureCluster + name: ${CLUSTER_NAME} +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: AzureCluster +metadata: + name: ${CLUSTER_NAME} + namespace: default +spec: + additionalTags: + buildProvenance: ${BUILD_PROVENANCE} + creationTimestamp: ${TIMESTAMP} + jobName: ${JOB_NAME} + extendedLocation: + name: ${AZURE_EXTENDEDLOCATION_NAME} + type: ${AZURE_EXTENDEDLOCATION_TYPE} + identityRef: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: AzureClusterIdentity + name: ${CLUSTER_IDENTITY_NAME} + location: ${AZURE_LOCATION_EDGEZONE} + networkSpec: + subnets: + - name: control-plane-subnet + role: control-plane + - name: node-subnet + role: node + vnet: + name: ${AZURE_VNET_NAME:=${CLUSTER_NAME}-vnet} + resourceGroup: ${AZURE_RESOURCE_GROUP:=${CLUSTER_NAME}} + subscriptionID: ${AZURE_SUBSCRIPTION_ID} +--- +apiVersion: controlplane.cluster.x-k8s.io/v1beta1 +kind: KubeadmControlPlane +metadata: + name: ${CLUSTER_NAME}-control-plane + namespace: default +spec: + kubeadmConfigSpec: + clusterConfiguration: + apiServer: + extraArgs: + cloud-config: /etc/kubernetes/azure.json + cloud-provider: external + feature-gates: MixedProtocolLBService=true + extraVolumes: + - hostPath: /etc/kubernetes/azure.json + mountPath: /etc/kubernetes/azure.json + name: cloud-config + readOnly: true + timeoutForControlPlane: 20m + controllerManager: + extraArgs: + allocate-node-cidrs: "false" + cloud-config: /etc/kubernetes/azure.json + cloud-provider: external + cluster-name: ${CLUSTER_NAME} + external-cloud-volume-plugin: azure + feature-gates: CSIMigrationAzureDisk=true + v: "4" + extraVolumes: + - hostPath: /etc/kubernetes/azure.json + mountPath: /etc/kubernetes/azure.json + name: cloud-config + readOnly: true + etcd: + local: + dataDir: /var/lib/etcddisk/etcd + extraArgs: + quota-backend-bytes: "8589934592" + diskSetup: + filesystems: + - device: /dev/disk/azure/scsi1/lun0 + extraOpts: + - -E + - lazy_itable_init=1,lazy_journal_init=1 + filesystem: ext4 + label: etcd_disk + - device: ephemeral0.1 + filesystem: ext4 + label: ephemeral0 + replaceFS: ntfs + partitions: + - device: /dev/disk/azure/scsi1/lun0 + layout: true + overwrite: false + tableType: gpt + files: + - contentFrom: + secret: + key: control-plane-azure.json + name: ${CLUSTER_NAME}-control-plane-azure-json + owner: root:root + path: /etc/kubernetes/azure.json + permissions: "0644" + initConfiguration: + nodeRegistration: + kubeletExtraArgs: + azure-container-registry-config: /etc/kubernetes/azure.json + cloud-config: /etc/kubernetes/azure.json + cloud-provider: external + feature-gates: CSIMigrationAzureDisk=true + name: '{{ ds.meta_data["local_hostname"] }}' + joinConfiguration: + nodeRegistration: + kubeletExtraArgs: + azure-container-registry-config: /etc/kubernetes/azure.json + cloud-config: /etc/kubernetes/azure.json + cloud-provider: external + feature-gates: CSIMigrationAzureDisk=true + name: '{{ ds.meta_data["local_hostname"] }}' + mounts: + - - LABEL=etcd_disk + - /var/lib/etcddisk + postKubeadmCommands: [] + preKubeadmCommands: [] + machineTemplate: + infrastructureRef: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: AzureMachineTemplate + name: ${CLUSTER_NAME}-control-plane + replicas: ${CONTROL_PLANE_MACHINE_COUNT} + version: ${KUBERNETES_VERSION} +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: AzureMachineTemplate +metadata: + name: ${CLUSTER_NAME}-control-plane + namespace: default +spec: + template: + spec: + dataDisks: + - diskSizeGB: 256 + lun: 0 + managedDisk: + storageAccountType: StandardSSD_LRS + nameSuffix: etcddisk + identity: UserAssigned + image: + id: /subscriptions/b9e38f20-7c9c-4497-a25d-1a0c5eef2108/resourceGroups/ClusterAPI-rg/providers/Microsoft.Compute/galleries/ImagesClusterAPI/images/capi-image + osDisk: + diskSizeGB: 128 + managedDisk: + storageAccountType: StandardSSD_LRS + osType: Linux + sshPublicKey: ${AZURE_SSH_PUBLIC_KEY_B64:=""} + userAssignedIdentities: + - providerID: /subscriptions/${AZURE_SUBSCRIPTION_ID}/resourceGroups/${CI_RG:=capz-ci}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/${USER_IDENTITY:=cloud-provider-user-identity} + vmSize: ${AZURE_EDGEZONE_CONTROL_PLANE_MACHINE_TYPE:=Standard_DS2_v2} +--- +apiVersion: cluster.x-k8s.io/v1beta1 +kind: MachineDeployment +metadata: + name: ${CLUSTER_NAME}-md-0 + namespace: default +spec: + clusterName: ${CLUSTER_NAME} + replicas: ${WORKER_MACHINE_COUNT} + selector: + matchLabels: null + template: + spec: + bootstrap: + configRef: + apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 + kind: KubeadmConfigTemplate + name: ${CLUSTER_NAME}-md-0 + clusterName: ${CLUSTER_NAME} + infrastructureRef: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: AzureMachineTemplate + name: ${CLUSTER_NAME}-md-0 + version: ${KUBERNETES_VERSION} +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: AzureMachineTemplate +metadata: + name: ${CLUSTER_NAME}-md-0 + namespace: default +spec: + template: + spec: + identity: UserAssigned + image: + id: /subscriptions/b9e38f20-7c9c-4497-a25d-1a0c5eef2108/resourceGroups/ClusterAPI-rg/providers/Microsoft.Compute/galleries/ImagesClusterAPI/images/capi-image + osDisk: + diskSizeGB: 128 + managedDisk: + storageAccountType: StandardSSD_LRS + osType: Linux + sshPublicKey: ${AZURE_SSH_PUBLIC_KEY_B64:=""} + userAssignedIdentities: + - providerID: /subscriptions/${AZURE_SUBSCRIPTION_ID}/resourceGroups/${CI_RG:=capz-ci}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/${USER_IDENTITY:=cloud-provider-user-identity} + vmSize: ${AZURE_EDGEZONE_NODE_MACHINE_TYPE:=Standard_DS4_v2} +--- +apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 +kind: KubeadmConfigTemplate +metadata: + name: ${CLUSTER_NAME}-md-0 + namespace: default +spec: + template: + spec: + files: + - contentFrom: + secret: + key: worker-node-azure.json + name: ${CLUSTER_NAME}-md-0-azure-json + owner: root:root + path: /etc/kubernetes/azure.json + permissions: "0644" + joinConfiguration: + nodeRegistration: + kubeletExtraArgs: + azure-container-registry-config: /etc/kubernetes/azure.json + cloud-config: /etc/kubernetes/azure.json + cloud-provider: external + feature-gates: CSIMigrationAzureDisk=true + name: '{{ ds.meta_data["local_hostname"] }}' + preKubeadmCommands: [] +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: AzureClusterIdentity +metadata: + labels: + clusterctl.cluster.x-k8s.io/move-hierarchy: "true" + name: ${CLUSTER_IDENTITY_NAME} + namespace: default +spec: + allowedNamespaces: {} + clientID: ${AZURE_CLIENT_ID} + clientSecret: + name: ${AZURE_CLUSTER_IDENTITY_SECRET_NAME} + namespace: ${AZURE_CLUSTER_IDENTITY_SECRET_NAMESPACE} + tenantID: ${AZURE_TENANT_ID} + type: ServicePrincipal diff --git a/templates/test/ci/prow-edgezone/kustomization.yaml b/templates/test/ci/prow-edgezone/kustomization.yaml new file mode 100644 index 000000000000..3818e93d0096 --- /dev/null +++ b/templates/test/ci/prow-edgezone/kustomization.yaml @@ -0,0 +1,15 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: default +resources: + - ../../../flavors/edgezone +patchesStrategicMerge: + - ../patches/tags.yaml + - ../patches/controller-manager.yaml + - ../patches/apiserver.yaml + - ../patches/uami-md-0.yaml + - ../patches/uami-control-plane.yaml + - patches/azurecluster-edgezone.yaml + - patches/sig-image.yaml + - patches/standardssd-disk.yaml + - patches/machine-type.yaml \ No newline at end of file diff --git a/templates/test/ci/prow-edgezone/patches/azurecluster-edgezone.yaml b/templates/test/ci/prow-edgezone/patches/azurecluster-edgezone.yaml new file mode 100644 index 000000000000..13d57100113f --- /dev/null +++ b/templates/test/ci/prow-edgezone/patches/azurecluster-edgezone.yaml @@ -0,0 +1,6 @@ +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: AzureCluster +metadata: + name: ${CLUSTER_NAME} +spec: + location: ${AZURE_LOCATION_EDGEZONE} diff --git a/templates/test/ci/prow-edgezone/patches/machine-type.yaml b/templates/test/ci/prow-edgezone/patches/machine-type.yaml new file mode 100644 index 000000000000..03a3d478f84f --- /dev/null +++ b/templates/test/ci/prow-edgezone/patches/machine-type.yaml @@ -0,0 +1,19 @@ +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: AzureMachineTemplate +metadata: + name: ${CLUSTER_NAME}-control-plane + namespace: default +spec: + template: + spec: + vmSize: ${AZURE_EDGEZONE_CONTROL_PLANE_MACHINE_TYPE:=Standard_DS2_v2} +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: AzureMachineTemplate +metadata: + name: ${CLUSTER_NAME}-md-0 + namespace: default +spec: + template: + spec: + vmSize: ${AZURE_EDGEZONE_NODE_MACHINE_TYPE:=Standard_DS4_v2} \ No newline at end of file diff --git a/templates/test/ci/prow-edgezone/patches/sig-image.yaml b/templates/test/ci/prow-edgezone/patches/sig-image.yaml new file mode 100644 index 000000000000..75ddc6c2d640 --- /dev/null +++ b/templates/test/ci/prow-edgezone/patches/sig-image.yaml @@ -0,0 +1,21 @@ +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: AzureMachineTemplate +metadata: + name: ${CLUSTER_NAME}-control-plane + namespace: default +spec: + template: + spec: + image: + id: "/subscriptions/b9e38f20-7c9c-4497-a25d-1a0c5eef2108/resourceGroups/ClusterAPI-rg/providers/Microsoft.Compute/galleries/ImagesClusterAPI/images/capi-image" +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: AzureMachineTemplate +metadata: + name: ${CLUSTER_NAME}-md-0 + namespace: default +spec: + template: + spec: + image: + id: "/subscriptions/b9e38f20-7c9c-4497-a25d-1a0c5eef2108/resourceGroups/ClusterAPI-rg/providers/Microsoft.Compute/galleries/ImagesClusterAPI/images/capi-image" \ No newline at end of file diff --git a/templates/test/ci/prow-edgezone/patches/standardssd-disk.yaml b/templates/test/ci/prow-edgezone/patches/standardssd-disk.yaml new file mode 100644 index 000000000000..2bfaae9129d2 --- /dev/null +++ b/templates/test/ci/prow-edgezone/patches/standardssd-disk.yaml @@ -0,0 +1,30 @@ +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: AzureMachineTemplate +metadata: + name: ${CLUSTER_NAME}-control-plane + namespace: default +spec: + template: + spec: + dataDisks: + - diskSizeGB: 256 + lun: 0 + nameSuffix: etcddisk + managedDisk: + storageAccountType: StandardSSD_LRS + osDisk: + managedDisk: + storageAccountType: StandardSSD_LRS +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: AzureMachineTemplate +metadata: + name: ${CLUSTER_NAME}-md-0 + namespace: default +spec: + template: + spec: + osDisk: + managedDisk: + storageAccountType: StandardSSD_LRS + \ No newline at end of file diff --git a/test/e2e/azure_edgezone.go b/test/e2e/azure_edgezone.go new file mode 100644 index 000000000000..71cfe6993214 --- /dev/null +++ b/test/e2e/azure_edgezone.go @@ -0,0 +1,103 @@ +//go:build e2e +// +build e2e + +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package e2e + +import ( + "context" + "strings" + + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2021-11-01/compute" + azureautorest "github.com/Azure/go-autorest/autorest/azure" + "github.com/Azure/go-autorest/autorest/azure/auth" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" + "sigs.k8s.io/cluster-api-provider-azure/azure" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + "sigs.k8s.io/cluster-api/test/framework" + "sigs.k8s.io/cluster-api/test/framework/clusterctl" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// AzureEdgeZoneClusterSpecInput is the input for Azure +type AzureEdgeZoneClusterSpecInput struct { + BootstrapClusterProxy framework.ClusterProxy + Namespace *corev1.Namespace + ClusterName string + E2EConfig *clusterctl.E2EConfig +} + +func AzureEdgeZoneClusterSpec(ctx context.Context, inputGetter func() AzureEdgeZoneClusterSpecInput) { + var ( + specName = "azure-edgezone-cluster" + input AzureEdgeZoneClusterSpecInput + ) + + input = inputGetter() + Expect(input).NotTo(BeNil()) + Expect(input.BootstrapClusterProxy).NotTo(BeNil(), "Invalid argument. input.BootstrapClusterProxy can't be nil when calling %s spec", specName) + Expect(input.Namespace).NotTo(BeNil(), "Invalid argument. input.Namespace can't be nil when calling %s spec", specName) + + By("creating a Kubernetes client to the workload cluster") + workloadClusterProxy := input.BootstrapClusterProxy.GetWorkloadCluster(ctx, input.Namespace.Name, input.ClusterName) + Expect(workloadClusterProxy).NotTo(BeNil()) + mgmtClient := bootstrapClusterProxy.GetClient() + Expect(mgmtClient).NotTo(BeNil()) + + By("Retrieving all machines from the machine template spec") + machineList := &infrav1.AzureMachineList{} + // list all of the requested objects within the cluster namespace with the cluster name label + Logf("Listing machines in namespace %s with label %s=%s", input.Namespace.Name, clusterv1.ClusterLabelName, workloadClusterProxy.GetName()) + err := mgmtClient.List(ctx, machineList, client.InNamespace(input.Namespace.Name), client.MatchingLabels{clusterv1.ClusterLabelName: workloadClusterProxy.GetName()}) + Expect(err).NotTo(HaveOccurred()) + + By("Getting extendedLocation Name and Type from environment variables or e2e config file") + extendedLocationType := input.E2EConfig.GetVariable(AzureExtendedLocationType) + extendedLocationName := input.E2EConfig.GetVariable(AzureExtendedLocationName) + + // get subscription id + settings, err := auth.GetSettingsFromEnvironment() + Expect(err).NotTo(HaveOccurred()) + subscriptionID := settings.GetSubscriptionID() + auth, err := settings.GetAuthorizer() + Expect(err).NotTo(HaveOccurred()) + + if len(machineList.Items) > 0 { + By("Creating a VM client") + // create a VM client + vmClient := compute.NewVirtualMachinesClient(subscriptionID) + vmClient.Authorizer = auth + + // get the resource group name + resourceID := strings.TrimPrefix(*machineList.Items[0].Spec.ProviderID, azure.ProviderIDPrefix) + resource, err := azureautorest.ParseResourceID(resourceID) + Expect(err).NotTo(HaveOccurred()) + + vmListResults, err := vmClient.List(ctx, resource.ResourceGroup, "") + Expect(err).NotTo(HaveOccurred()) + + By("Verifying VMs' extendedLocation property is correct") + for _, machine := range vmListResults.Values() { + Expect(*machine.ExtendedLocation.Name).To(Equal(extendedLocationName)) + Expect(string(machine.ExtendedLocation.Type)).To(Equal(extendedLocationType)) + } + } +} diff --git a/test/e2e/azure_test.go b/test/e2e/azure_test.go index a05cdc0c969d..f15c12efa07d 100644 --- a/test/e2e/azure_test.go +++ b/test/e2e/azure_test.go @@ -865,4 +865,49 @@ var _ = Describe("Workload cluster creation", func() { By("PASSED!") }) }) + + // ci-e2e.sh and Prow CI skip this test by default. To include this test, set `GINKGO_SKIP=""`. + // This spec expects a user-assigned identity named "cloud-provider-user-identity" in a "capz-ci" + // resource group. Override these defaults by setting the USER_IDENTITY and CI_RG environment variables. + // You can also override the default SKU `Standard_DS2_v2` and `Standard_DS4_v2` storage by setting + // the `AZURE_EDGEZONE_CONTROL_PLANE_MACHINE_TYPE` and `AZURE_EDGEZONE_NODE_MACHINE_TYPE` environment variables. + Context("Creating clusters on public MEC [OPTIONAL]", func() { + It("with 1 control plane nodes and 1 worker node", func() { + By("using user-assigned identity") + clusterName = getClusterName(clusterNamePrefix, "edgezone") + clusterctl.ApplyClusterTemplateAndWait(ctx, createApplyClusterTemplateInput( + specName, + withFlavor("edgezone"), + withNamespace(namespace.Name), + withClusterName(clusterName), + withControlPlaneMachineCount(1), + withWorkerMachineCount(1), + withControlPlaneWaiters(clusterctl.ControlPlaneWaiters{ + WaitForControlPlaneInitialized: EnsureControlPlaneInitialized, + }), + withPostMachinesProvisioned(func() { + EnsureDaemonsets(ctx, func() DaemonsetsSpecInput { + return DaemonsetsSpecInput{ + BootstrapClusterProxy: bootstrapClusterProxy, + Namespace: namespace, + ClusterName: clusterName, + } + }) + }), + ), result) + + By("Verifying extendedLocation property in Azure VMs is corresponding to extendedLocation property in edgezone yaml file", func() { + AzureEdgeZoneClusterSpec(ctx, func() AzureEdgeZoneClusterSpecInput { + return AzureEdgeZoneClusterSpecInput{ + BootstrapClusterProxy: bootstrapClusterProxy, + Namespace: namespace, + ClusterName: clusterName, + E2EConfig: e2eConfig, + } + }) + }) + + By("PASSED!") + }) + }) }) diff --git a/test/e2e/common.go b/test/e2e/common.go index 6ec8358681b6..df3a7b158afa 100644 --- a/test/e2e/common.go +++ b/test/e2e/common.go @@ -55,6 +55,8 @@ const ( AddonsPath = "ADDONS_PATH" RedactLogScriptPath = "REDACT_LOG_SCRIPT" AzureLocation = "AZURE_LOCATION" + AzureExtendedLocationType = "AZURE_EXTENDEDLOCATION_TYPE" + AzureExtendedLocationName = "AZURE_EXTENDEDLOCATION_NAME" AzureResourceGroup = "AZURE_RESOURCE_GROUP" AzureVNetName = "AZURE_VNET_NAME" AzureCustomVNetName = "AZURE_CUSTOM_VNET_NAME" @@ -150,13 +152,6 @@ func dumpSpecResourcesAndCleanup(ctx context.Context, input cleanupInput) { redactLogs() }() - if input.Cluster == nil { - By("Unable to dump workload cluster logs as the cluster is nil") - } else if !input.SkipLogCollection { - Byf("Dumping logs from the %q workload cluster", input.Cluster.Name) - input.ClusterProxy.CollectWorkloadClusterLogs(ctx, input.Cluster.Namespace, input.Cluster.Name, filepath.Join(input.ArtifactFolder, "clusters", input.Cluster.Name)) - } - Logf("Dumping all the Cluster API resources in the %q namespace", input.Namespace.Name) // Dump all Cluster API related resources to artifacts before deleting them. framework.DumpAllResources(ctx, framework.DumpAllResourcesInput{ @@ -165,6 +160,13 @@ func dumpSpecResourcesAndCleanup(ctx context.Context, input cleanupInput) { LogPath: filepath.Join(input.ArtifactFolder, "clusters", input.ClusterProxy.GetName(), "resources"), }) + if input.Cluster == nil { + By("Unable to dump workload cluster logs as the cluster is nil") + } else if !input.SkipLogCollection { + Byf("Dumping logs from the %q workload cluster", input.Cluster.Name) + input.ClusterProxy.CollectWorkloadClusterLogs(ctx, input.Cluster.Namespace, input.Cluster.Name, filepath.Join(input.ArtifactFolder, "clusters", input.Cluster.Name)) + } + if input.SkipCleanup { return } diff --git a/test/e2e/config/azure-dev.yaml b/test/e2e/config/azure-dev.yaml index 6e6befef9822..19d6d9f27393 100644 --- a/test/e2e/config/azure-dev.yaml +++ b/test/e2e/config/azure-dev.yaml @@ -146,6 +146,8 @@ providers: targetName: "cluster-template-topology.yaml" - sourcePath: "${PWD}/templates/test/ci/cluster-template-prow-flatcar.yaml" targetName: "cluster-template-flatcar.yaml" + - sourcePath: "${PWD}/templates/test/ci/cluster-template-prow-edgezone.yaml" + targetName: "cluster-template-edgezone.yaml" replacements: - old: "--v=0" new: "--v=2" @@ -166,8 +168,11 @@ variables: EXP_AKS_RESOURCE_HEALTH: "true" EXP_MACHINE_POOL: "true" EXP_CLUSTER_RESOURCE_SET: "true" + EXP_EDGEZONE: "true" CLUSTER_TOPOLOGY: "true" EXP_KUBEADM_BOOTSTRAP_FORMAT_IGNITION: "true" + AZURE_EXTENDEDLOCATION_TYPE: "${AZURE_EXTENDEDLOCATION_TYPE:-EdgeZone}" + AZURE_EXTENDEDLOCATION_NAME: "${AZURE_EXTENDEDLOCATION_NAME:-microsoftvancouver1}" CONFORMANCE_WORKER_MACHINE_COUNT: "2" CONFORMANCE_CONTROL_PLANE_MACHINE_COUNT: "${CONFORMANCE_CONTROL_PLANE_MACHINE_COUNT:-1}" CONFORMANCE_IMAGE: "${CONFORMANCE_IMAGE:-}"