diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 65d0deaa..a471ffdd 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -88,6 +88,7 @@ jobs: REQUEST_TOKEN=$ACTIONS_ID_TOKEN_REQUEST_TOKEN REQUEST_URI=$ACTIONS_ID_TOKEN_REQUEST_URL FED_TOKEN=$(curl -H "Authorization: bearer $REQUEST_TOKEN" "${REQUEST_URI}&audience=api://AzureADTokenExchange" | jq .value -r) + echo "FED_TOKEN=$FED_TOKEN" >> $GITHUB_ENV az login --service-principal -u ${{ secrets.E2E_AZURE_CLIENT_ID }} -t ${{ secrets.AZURE_TENANT_ID }} --federated-token $FED_TOKEN --output none sleep 240 done & @@ -108,6 +109,9 @@ jobs: AZURE_NETWORK_SETTING: ${{ matrix.network-setting }} AZURE_RESOURCE_GROUP: ${{ env.AZURE_RESOURCE_GROUP }} ENABLE_TRAFFIC_MANAGER: ${{ matrix.enable-traffic-manager }} + AZURE_CLIENT_ID: ${{ secrets.E2E_AZURE_CLIENT_ID}} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + FED_TOKEN: ${{ env.FED_TOKEN }} - name: Cleanup e2e if: always() run: | diff --git a/go.mod b/go.mod index 6c0c0803..664a4283 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.22.7 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4 v4.3.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/trafficmanager/armtrafficmanager v1.3.0 github.com/google/go-cmp v0.6.0 @@ -26,7 +27,6 @@ require go.goms.io/fleet v0.11.4 require ( github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.7.0 // indirect diff --git a/pkg/controllers/hub/trafficmanagerprofile/controller.go b/pkg/controllers/hub/trafficmanagerprofile/controller.go index ec3904ea..9fd09c43 100644 --- a/pkg/controllers/hub/trafficmanagerprofile/controller.go +++ b/pkg/controllers/hub/trafficmanagerprofile/controller.go @@ -37,6 +37,11 @@ const ( DNSRelativeNameFormat = "%s-%s" // AzureResourceProfileNameFormat is the name format of the Azure Traffic Manager Profile created by the fleet controller. AzureResourceProfileNameFormat = "fleet-%s" + + // DefaultDNSTTL is in seconds. This informs the local DNS resolvers and DNS clients how long to cache DNS responses + // provided by this Traffic Manager profile. + // Defaults to 60 which is the same as the portal's default config. + DefaultDNSTTL = int64(60) ) var ( @@ -166,12 +171,12 @@ func (r *Reconciler) handleUpdate(ctx context.Context, profile *fleetnetv1alpha1 return r.updateProfileStatus(ctx, profile, res.Profile, updateErr) } -// EqualAzureTrafficManagerProfile compares only few fields of the current and desired Azure Traffic Manager profiles +// EqualAzureTrafficManagerProfile compares only few fields of the buildCurrentFunc and desired Azure Traffic Manager profiles // by ignoring others. // The desired profile is built by the controllers and all the required fields should not be nil. func EqualAzureTrafficManagerProfile(current, desired armtrafficmanager.Profile) bool { - // location and dnsConfig is not immutable - if current.Properties == nil || current.Properties.MonitorConfig == nil || current.Properties.ProfileStatus == nil || current.Properties.TrafficRoutingMethod == nil { + // location and dnsConfig (excluding TTL) is not immutable + if current.Properties == nil || current.Properties.MonitorConfig == nil || current.Properties.ProfileStatus == nil || current.Properties.TrafficRoutingMethod == nil || current.Properties.DNSConfig == nil { return false } @@ -194,6 +199,10 @@ func EqualAzureTrafficManagerProfile(current, desired armtrafficmanager.Profile) return false } + if current.Properties.DNSConfig.TTL == nil || *current.Properties.DNSConfig.TTL != *desired.Properties.DNSConfig.TTL { + return false + } + if current.Tags == nil { return false } @@ -271,6 +280,7 @@ func generateAzureTrafficManagerProfile(profile *fleetnetv1alpha1.TrafficManager Properties: &armtrafficmanager.ProfileProperties{ DNSConfig: &armtrafficmanager.DNSConfig{ RelativeName: ptr.To(fmt.Sprintf(DNSRelativeNameFormat, profile.Namespace, profile.Name)), + TTL: ptr.To(DefaultDNSTTL), // no default value on the server side, using 60s same as portal's default config }, MonitorConfig: &armtrafficmanager.MonitorConfig{ IntervalInSeconds: mc.IntervalInSeconds, diff --git a/pkg/controllers/hub/trafficmanagerprofile/controller_test.go b/pkg/controllers/hub/trafficmanagerprofile/controller_test.go index 3fa0a8db..12723533 100644 --- a/pkg/controllers/hub/trafficmanagerprofile/controller_test.go +++ b/pkg/controllers/hub/trafficmanagerprofile/controller_test.go @@ -28,370 +28,251 @@ func TestGenerateAzureTrafficManagerProfileName(t *testing.T) { } } +func buildDesiredProfile() armtrafficmanager.Profile { + return armtrafficmanager.Profile{ + Location: ptr.To("global"), + Properties: &armtrafficmanager.ProfileProperties{ + DNSConfig: &armtrafficmanager.DNSConfig{ + RelativeName: ptr.To("namespace-name"), + TTL: ptr.To(int64(60)), + }, + MonitorConfig: &armtrafficmanager.MonitorConfig{ + IntervalInSeconds: ptr.To[int64](30), + Path: ptr.To("/path"), + Port: ptr.To[int64](80), + Protocol: ptr.To(armtrafficmanager.MonitorProtocolHTTP), + TimeoutInSeconds: ptr.To[int64](10), + ToleratedNumberOfFailures: ptr.To[int64](3), + }, + ProfileStatus: ptr.To(armtrafficmanager.ProfileStatusEnabled), + TrafficRoutingMethod: ptr.To(armtrafficmanager.TrafficRoutingMethodWeighted), + }, + Tags: map[string]*string{ + "tagKey": ptr.To("tagValue"), + }, + } +} + func TestEqualAzureTrafficManagerProfile(t *testing.T) { tests := []struct { - name string - current armtrafficmanager.Profile - want bool + name string + buildCurrentFunc func() armtrafficmanager.Profile + want bool }{ { - name: "Profiles are equal though current profile has some different fields from the desired", - current: armtrafficmanager.Profile{ - ID: ptr.To("abc"), - Location: ptr.To("global"), - Properties: &armtrafficmanager.ProfileProperties{ - DNSConfig: &armtrafficmanager.DNSConfig{ - RelativeName: ptr.To("namespace-name"), - }, - MonitorConfig: &armtrafficmanager.MonitorConfig{ - IntervalInSeconds: ptr.To[int64](30), - Path: ptr.To("/path"), - Port: ptr.To[int64](80), - Protocol: ptr.To(armtrafficmanager.MonitorProtocolHTTP), - TimeoutInSeconds: ptr.To[int64](10), - ToleratedNumberOfFailures: ptr.To[int64](3), - }, - ProfileStatus: ptr.To(armtrafficmanager.ProfileStatusEnabled), - TrafficRoutingMethod: ptr.To(armtrafficmanager.TrafficRoutingMethodWeighted), - MaxReturn: ptr.To(int64(1)), - TrafficViewEnrollmentStatus: ptr.To(armtrafficmanager.TrafficViewEnrollmentStatusDisabled), - }, - Tags: map[string]*string{ + name: "Profiles are equal though buildCurrentFunc profile has some different fields from the desired", + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.ID = ptr.To("abc") + res.Tags = map[string]*string{ "tagKey": ptr.To("tagValue"), "otherKey": ptr.To("otherValue"), - }, + } + res.Properties.MaxReturn = ptr.To(int64(1)) + return res }, want: true, }, { name: "properties is nil", - current: armtrafficmanager.Profile{ - ID: ptr.To("abc"), + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Properties = nil + return res }, }, { name: "MonitorConfig is nil", - current: armtrafficmanager.Profile{ - Properties: &armtrafficmanager.ProfileProperties{}, + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Properties.MonitorConfig = nil + return res }, }, { name: "ProfileStatus is nil", - current: armtrafficmanager.Profile{ - Properties: &armtrafficmanager.ProfileProperties{ - MonitorConfig: &armtrafficmanager.MonitorConfig{}, - }, + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Properties.ProfileStatus = nil + return res }, }, { name: "TrafficRoutingMethod is nil", - current: armtrafficmanager.Profile{ - Properties: &armtrafficmanager.ProfileProperties{ - MonitorConfig: &armtrafficmanager.MonitorConfig{}, - ProfileStatus: ptr.To(armtrafficmanager.ProfileStatusEnabled), - }, + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Properties.TrafficRoutingMethod = nil + return res }, }, { - name: "MonitorConfig.IntervalInSeconds is nil", - current: armtrafficmanager.Profile{ - Properties: &armtrafficmanager.ProfileProperties{ - MonitorConfig: &armtrafficmanager.MonitorConfig{}, - ProfileStatus: ptr.To(armtrafficmanager.ProfileStatusEnabled), - TrafficRoutingMethod: ptr.To(armtrafficmanager.TrafficRoutingMethodWeighted), - }, + name: "DNSConfig is nil", + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Properties.DNSConfig = nil + return res }, }, { name: "MonitorConfig.IntervalInSeconds is nil", - current: armtrafficmanager.Profile{ - Properties: &armtrafficmanager.ProfileProperties{ - MonitorConfig: &armtrafficmanager.MonitorConfig{}, - ProfileStatus: ptr.To(armtrafficmanager.ProfileStatusEnabled), - TrafficRoutingMethod: ptr.To(armtrafficmanager.TrafficRoutingMethodWeighted), - }, + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Properties.MonitorConfig.IntervalInSeconds = nil + return res }, }, { name: "MonitorConfig.Path is nil", - current: armtrafficmanager.Profile{ - Properties: &armtrafficmanager.ProfileProperties{ - MonitorConfig: &armtrafficmanager.MonitorConfig{ - IntervalInSeconds: ptr.To[int64](30), - }, - ProfileStatus: ptr.To(armtrafficmanager.ProfileStatusEnabled), - TrafficRoutingMethod: ptr.To(armtrafficmanager.TrafficRoutingMethodWeighted), - }, + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Properties.MonitorConfig.Path = nil + return res }, }, { name: "MonitorConfig.Port is nil", - current: armtrafficmanager.Profile{ - Properties: &armtrafficmanager.ProfileProperties{ - MonitorConfig: &armtrafficmanager.MonitorConfig{ - IntervalInSeconds: ptr.To[int64](30), - Path: ptr.To("/path"), - }, - ProfileStatus: ptr.To(armtrafficmanager.ProfileStatusEnabled), - TrafficRoutingMethod: ptr.To(armtrafficmanager.TrafficRoutingMethodWeighted), - }, + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Properties.MonitorConfig.Port = nil + return res }, }, { name: "MonitorConfig.Protocol is nil", - current: armtrafficmanager.Profile{ - Properties: &armtrafficmanager.ProfileProperties{ - MonitorConfig: &armtrafficmanager.MonitorConfig{ - IntervalInSeconds: ptr.To[int64](30), - Path: ptr.To("/path"), - Port: ptr.To[int64](80), - }, - ProfileStatus: ptr.To(armtrafficmanager.ProfileStatusEnabled), - TrafficRoutingMethod: ptr.To(armtrafficmanager.TrafficRoutingMethodWeighted), - }, + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Properties.MonitorConfig.Protocol = nil + return res }, }, { name: "MonitorConfig.TimeoutInSeconds is nil", - current: armtrafficmanager.Profile{ - Properties: &armtrafficmanager.ProfileProperties{ - MonitorConfig: &armtrafficmanager.MonitorConfig{ - IntervalInSeconds: ptr.To[int64](30), - Path: ptr.To("/path"), - Port: ptr.To[int64](80), - Protocol: ptr.To(armtrafficmanager.MonitorProtocolHTTP), - }, - ProfileStatus: ptr.To(armtrafficmanager.ProfileStatusEnabled), - TrafficRoutingMethod: ptr.To(armtrafficmanager.TrafficRoutingMethodWeighted), - }, + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Properties.MonitorConfig.TimeoutInSeconds = nil + return res }, }, { name: "MonitorConfig.ToleratedNumberOfFailures is nil", - current: armtrafficmanager.Profile{ - Properties: &armtrafficmanager.ProfileProperties{ - MonitorConfig: &armtrafficmanager.MonitorConfig{ - IntervalInSeconds: ptr.To[int64](30), - Path: ptr.To("/path"), - Port: ptr.To[int64](80), - Protocol: ptr.To(armtrafficmanager.MonitorProtocolHTTP), - TimeoutInSeconds: ptr.To[int64](10), - }, - ProfileStatus: ptr.To(armtrafficmanager.ProfileStatusEnabled), - TrafficRoutingMethod: ptr.To(armtrafficmanager.TrafficRoutingMethodWeighted), - }, + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Properties.MonitorConfig.ToleratedNumberOfFailures = nil + return res }, }, { name: "MonitorConfig.IntervalInSeconds is different", - current: armtrafficmanager.Profile{ - Properties: &armtrafficmanager.ProfileProperties{ - MonitorConfig: &armtrafficmanager.MonitorConfig{ - IntervalInSeconds: ptr.To[int64](10), - Path: ptr.To("/path"), - Port: ptr.To[int64](80), - Protocol: ptr.To(armtrafficmanager.MonitorProtocolHTTP), - TimeoutInSeconds: ptr.To[int64](10), - ToleratedNumberOfFailures: ptr.To[int64](3), - }, - ProfileStatus: ptr.To(armtrafficmanager.ProfileStatusEnabled), - TrafficRoutingMethod: ptr.To(armtrafficmanager.TrafficRoutingMethodWeighted), - }, + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Properties.MonitorConfig.IntervalInSeconds = ptr.To[int64](10) + return res }, }, { name: "MonitorConfig.Path is different", - current: armtrafficmanager.Profile{ - Properties: &armtrafficmanager.ProfileProperties{ - MonitorConfig: &armtrafficmanager.MonitorConfig{ - IntervalInSeconds: ptr.To[int64](30), - Path: ptr.To("/invalid-path"), - Port: ptr.To[int64](80), - Protocol: ptr.To(armtrafficmanager.MonitorProtocolHTTP), - TimeoutInSeconds: ptr.To[int64](10), - ToleratedNumberOfFailures: ptr.To[int64](3), - }, - ProfileStatus: ptr.To(armtrafficmanager.ProfileStatusEnabled), - TrafficRoutingMethod: ptr.To(armtrafficmanager.TrafficRoutingMethodWeighted), - }, + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Properties.MonitorConfig.Path = ptr.To("/invalid-path") + return res }, }, { name: "MonitorConfig.Port is different", - current: armtrafficmanager.Profile{ - Properties: &armtrafficmanager.ProfileProperties{ - MonitorConfig: &armtrafficmanager.MonitorConfig{ - IntervalInSeconds: ptr.To[int64](30), - Path: ptr.To("/path"), - Port: ptr.To[int64](8080), - Protocol: ptr.To(armtrafficmanager.MonitorProtocolHTTP), - TimeoutInSeconds: ptr.To[int64](10), - ToleratedNumberOfFailures: ptr.To[int64](3), - }, - }, + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Properties.MonitorConfig.Port = ptr.To[int64](8080) + return res }, }, { name: "MonitorConfig.Protocol is different", - current: armtrafficmanager.Profile{ - Properties: &armtrafficmanager.ProfileProperties{ - MonitorConfig: &armtrafficmanager.MonitorConfig{ - IntervalInSeconds: ptr.To[int64](30), - Path: ptr.To("/path"), - Port: ptr.To[int64](80), - Protocol: ptr.To(armtrafficmanager.MonitorProtocolHTTPS), - TimeoutInSeconds: ptr.To[int64](10), - ToleratedNumberOfFailures: ptr.To[int64](3), - }, - }, + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Properties.MonitorConfig.Protocol = ptr.To(armtrafficmanager.MonitorProtocolHTTPS) + return res }, }, { name: "MonitorConfig.TimeoutInSeconds is different", - current: armtrafficmanager.Profile{ - Properties: &armtrafficmanager.ProfileProperties{ - MonitorConfig: &armtrafficmanager.MonitorConfig{ - IntervalInSeconds: ptr.To[int64](30), - Path: ptr.To("/path"), - Port: ptr.To[int64](80), - Protocol: ptr.To(armtrafficmanager.MonitorProtocolHTTP), - TimeoutInSeconds: ptr.To[int64](30), - ToleratedNumberOfFailures: ptr.To[int64](3), - }, - }, + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Properties.MonitorConfig.TimeoutInSeconds = ptr.To[int64](30) + return res }, }, { name: "MonitorConfig.ToleratedNumberOfFailures is different", - current: armtrafficmanager.Profile{ - Properties: &armtrafficmanager.ProfileProperties{ - MonitorConfig: &armtrafficmanager.MonitorConfig{ - IntervalInSeconds: ptr.To[int64](30), - Path: ptr.To("/path"), - Port: ptr.To[int64](80), - Protocol: ptr.To(armtrafficmanager.MonitorProtocolHTTP), - TimeoutInSeconds: ptr.To[int64](10), - ToleratedNumberOfFailures: ptr.To[int64](4), - }, - }, + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Properties.MonitorConfig.ToleratedNumberOfFailures = ptr.To[int64](4) + return res }, }, { name: "ProfileStatus is different", - current: armtrafficmanager.Profile{ - Properties: &armtrafficmanager.ProfileProperties{ - MonitorConfig: &armtrafficmanager.MonitorConfig{ - IntervalInSeconds: ptr.To[int64](30), - Path: ptr.To("/path"), - Port: ptr.To[int64](80), - Protocol: ptr.To(armtrafficmanager.MonitorProtocolHTTP), - TimeoutInSeconds: ptr.To[int64](10), - ToleratedNumberOfFailures: ptr.To[int64](3), - }, - ProfileStatus: ptr.To(armtrafficmanager.ProfileStatusDisabled), - }, + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Properties.ProfileStatus = ptr.To(armtrafficmanager.ProfileStatusDisabled) + return res }, }, { name: "TrafficMethod is different", - current: armtrafficmanager.Profile{ - Properties: &armtrafficmanager.ProfileProperties{ - MonitorConfig: &armtrafficmanager.MonitorConfig{ - IntervalInSeconds: ptr.To[int64](30), - Path: ptr.To("/path"), - Port: ptr.To[int64](80), - Protocol: ptr.To(armtrafficmanager.MonitorProtocolHTTP), - TimeoutInSeconds: ptr.To[int64](10), - ToleratedNumberOfFailures: ptr.To[int64](3), - }, - ProfileStatus: ptr.To(armtrafficmanager.ProfileStatusEnabled), - TrafficRoutingMethod: ptr.To(armtrafficmanager.TrafficRoutingMethodPriority), - }, + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Properties.TrafficRoutingMethod = ptr.To(armtrafficmanager.TrafficRoutingMethodPriority) + return res + }, + }, + { + name: "DNS TTL is nil", + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Properties.DNSConfig.TTL = nil + return res + }, + }, + { + name: "DNS TTL is different", + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Properties.DNSConfig.TTL = ptr.To(int64(10)) + return res }, }, { name: "Tags is nil", - current: armtrafficmanager.Profile{ - Properties: &armtrafficmanager.ProfileProperties{ - MonitorConfig: &armtrafficmanager.MonitorConfig{ - IntervalInSeconds: ptr.To[int64](30), - Path: ptr.To("/path"), - Port: ptr.To[int64](80), - Protocol: ptr.To(armtrafficmanager.MonitorProtocolHTTP), - TimeoutInSeconds: ptr.To[int64](10), - ToleratedNumberOfFailures: ptr.To[int64](3), - }, - ProfileStatus: ptr.To(armtrafficmanager.ProfileStatusEnabled), - TrafficRoutingMethod: ptr.To(armtrafficmanager.TrafficRoutingMethodWeighted), - }, + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Tags = nil + return res }, }, { name: "Tag key is missing", - current: armtrafficmanager.Profile{ - Properties: &armtrafficmanager.ProfileProperties{ - MonitorConfig: &armtrafficmanager.MonitorConfig{ - IntervalInSeconds: ptr.To[int64](30), - Path: ptr.To("/path"), - Port: ptr.To[int64](80), - Protocol: ptr.To(armtrafficmanager.MonitorProtocolHTTP), - TimeoutInSeconds: ptr.To[int64](10), - ToleratedNumberOfFailures: ptr.To[int64](3), - }, - ProfileStatus: ptr.To(armtrafficmanager.ProfileStatusEnabled), - TrafficRoutingMethod: ptr.To(armtrafficmanager.TrafficRoutingMethodWeighted), - }, - Tags: map[string]*string{ + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Tags = map[string]*string{ "otherKey": ptr.To("otherValue"), - }, + } + return res }, }, { name: "Tag value is different", - current: armtrafficmanager.Profile{ - Properties: &armtrafficmanager.ProfileProperties{ - MonitorConfig: &armtrafficmanager.MonitorConfig{ - IntervalInSeconds: ptr.To[int64](30), - Path: ptr.To("/path"), - Port: ptr.To[int64](80), - Protocol: ptr.To(armtrafficmanager.MonitorProtocolHTTP), - TimeoutInSeconds: ptr.To[int64](10), - ToleratedNumberOfFailures: ptr.To[int64](3), - }, - ProfileStatus: ptr.To(armtrafficmanager.ProfileStatusEnabled), - TrafficRoutingMethod: ptr.To(armtrafficmanager.TrafficRoutingMethodWeighted), - }, - Tags: map[string]*string{ - "tagKey": ptr.To("otherValue"), - "otherKey": ptr.To("otherValue"), - }, + buildCurrentFunc: func() armtrafficmanager.Profile { + res := buildDesiredProfile() + res.Tags["tagKey"] = ptr.To("otherValue") + return res }, }, } - desired := armtrafficmanager.Profile{ - Location: ptr.To("global"), - Properties: &armtrafficmanager.ProfileProperties{ - DNSConfig: &armtrafficmanager.DNSConfig{ - RelativeName: ptr.To("namespace-name"), - }, - MonitorConfig: &armtrafficmanager.MonitorConfig{ - IntervalInSeconds: ptr.To[int64](30), - Path: ptr.To("/path"), - Port: ptr.To[int64](80), - Protocol: ptr.To(armtrafficmanager.MonitorProtocolHTTP), - TimeoutInSeconds: ptr.To[int64](10), - ToleratedNumberOfFailures: ptr.To[int64](3), - }, - ProfileStatus: ptr.To(armtrafficmanager.ProfileStatusEnabled), - TrafficRoutingMethod: ptr.To(armtrafficmanager.TrafficRoutingMethodWeighted), - }, - Tags: map[string]*string{ - "tagKey": ptr.To("tagValue"), - }, - } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := EqualAzureTrafficManagerProfile(tt.current, desired); got != tt.want { + desired := buildDesiredProfile() + if got := EqualAzureTrafficManagerProfile(tt.buildCurrentFunc(), desired); got != tt.want { t.Errorf("EqualAzureTrafficManagerProfile() = %v, want %v", got, tt.want) } }) diff --git a/test/README.md b/test/README.md index 81c71e1b..04dd2a58 100644 --- a/test/README.md +++ b/test/README.md @@ -22,6 +22,7 @@ export AZURE_SUBSCRIPTION_ID= # and detailed explanations for each network setting are provided in the scripts under folder "test/scripts". export AZURE_NETWORK_SETTING=shared-vnet export ENABLE_TRAFFIC_MANAGER=true +export USE_DEFAULT_AZURE_CLIENTS=true ``` Run Makefile Target to setup e2e environment: diff --git a/test/common/trafficmanager/azureprovider/profile.go b/test/common/trafficmanager/azureprovider/profile.go new file mode 100644 index 00000000..03f9cbb9 --- /dev/null +++ b/test/common/trafficmanager/azureprovider/profile.go @@ -0,0 +1,37 @@ +/* +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. +*/ + +// Package azureprovider provides utils to interact with azure traffic manager resources. +package azureprovider + +import ( + "context" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/trafficmanager/armtrafficmanager" + . "github.com/onsi/gomega" +) + +var ( + cmpProfileOptions = cmp.Options{ + cmpopts.IgnoreFields(armtrafficmanager.Profile{}, "ID", "Name", "Type"), + cmpopts.IgnoreFields(armtrafficmanager.MonitorConfig{}, "ProfileMonitorStatus"), // cannot predict the monitor status + } +) + +// Validator contains the way of accessing the Azure Traffic Manager resources. +type Validator struct { + ProfileClient *armtrafficmanager.ProfilesClient + EndpointClient *armtrafficmanager.EndpointsClient + ResourceGroup string +} + +func (v *Validator) ValidateProfile(ctx context.Context, name string, want armtrafficmanager.Profile) { + res, err := v.ProfileClient.Get(ctx, v.ResourceGroup, name, nil) + Expect(err).Should(Succeed(), "Failed to get the traffic manager profile") + diff := cmp.Diff(want, res.Profile, cmpProfileOptions) + Expect(diff).Should(BeEmpty(), "trafficManagerProfile mismatch (-want, +got) :\n%s", diff) +} diff --git a/test/common/trafficmanager/validator/profile.go b/test/common/trafficmanager/validator/profile.go index 2143b01f..b3f26bc0 100644 --- a/test/common/trafficmanager/validator/profile.go +++ b/test/common/trafficmanager/validator/profile.go @@ -3,6 +3,7 @@ Copyright (c) Microsoft Corporation. Licensed under the MIT license. */ +// Package validator provides the validation functions for the k8 traffic manager object. package validator import ( diff --git a/test/e2e/azuretrafficmanager_utils.go b/test/e2e/azuretrafficmanager_utils.go new file mode 100644 index 00000000..d02254e7 --- /dev/null +++ b/test/e2e/azuretrafficmanager_utils.go @@ -0,0 +1,6 @@ +/* +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. +*/ + +package e2e diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index b9c373a7..f749d1e7 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -8,6 +8,11 @@ package e2e import ( "context" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/trafficmanager/armtrafficmanager" + "go.goms.io/fleet-networking/test/common/trafficmanager/azureprovider" + "os" "testing" . "github.com/onsi/ginkgo/v2" @@ -24,6 +29,19 @@ import ( "go.goms.io/fleet-networking/test/e2e/framework" ) +const ( + // common environments required by both ci pipeline and local development + azureSubscriptionEnv = "AZURE_SUBSCRIPTION_ID" + azureTrafficManagerResourceGroupEnv = "AZURE_RESOURCE_GROUP" + + // environments required by the ci pipeline + azureClientIDEnv = "AZURE_CLIENT_ID" + azureTenantIDEnv = "AZURE_TENANT_ID" + federatedTokenEnv = "FED_TOKEN" + + useDefaultAzureClientsEnv = "USE_DEFAULT_AZURE_CLIENTS" +) + var ( hubClusterName = "hub" memberClusterNames = []string{"member-1", "member-2"} @@ -36,6 +54,8 @@ var ( scheme = runtime.NewScheme() ctx = context.Background() + + atmValidator *azureprovider.Validator ) func init() { @@ -67,8 +87,48 @@ var _ = BeforeSuite(func() { testNamespace = framework.UniqueTestNamespace() createTestNamespace(context.Background()) + initAzureClients() }) +func initAzureClients() { + subscriptionID := os.Getenv(azureSubscriptionEnv) + Expect(subscriptionID).ShouldNot(BeEmpty(), "Azure subscription ID is not set") + + atmResourceGroup := os.Getenv(azureTrafficManagerResourceGroupEnv) + Expect(atmResourceGroup).ShouldNot(BeEmpty(), "Azure traffic manager resource group is not set") + + //createDefaultAzureClients := os.Getenv(useDefaultAzureClientsEnv) + var cred azcore.TokenCredential + var err error + //if createDefaultAzureClients == "true" { + cred, err = azidentity.NewDefaultAzureCredential(nil) + Expect(err).Should(Succeed(), "Failed to obtain default Azure credential") + //} else { + // clientID := os.Getenv(azureClientIDEnv) + // Expect(clientID).ShouldNot(BeEmpty(), "Azure client ID is not set") + // tenantID := os.Getenv(azureTenantIDEnv) + // Expect(tenantID).ShouldNot(BeEmpty(), "Azure tenant ID is not set") + // + // options := &azidentity.ClientAssertionCredentialOptions{} + // cred, err = azidentity.NewClientAssertionCredential( + // tenantID, + // clientID, + // func(ctx context.Context) (string, error) { + // return os.Getenv(federatedTokenEnv), nil + // }, + // options, + // ) + // Expect(err).Should(Succeed(), "Failed to obtain Azure credential") + //} + clientFactory, err := armtrafficmanager.NewClientFactory(subscriptionID, cred, nil) + Expect(err).Should(Succeed(), "Failed to create client") + atmValidator = &azureprovider.Validator{ + ProfileClient: clientFactory.NewProfilesClient(), + EndpointClient: clientFactory.NewEndpointsClient(), + ResourceGroup: atmResourceGroup, + } +} + func createTestNamespace(ctx context.Context) { ns := corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ diff --git a/test/e2e/framework/workload_manager.go b/test/e2e/framework/workload_manager.go index 9b254f92..6e12be87 100644 --- a/test/e2e/framework/workload_manager.go +++ b/test/e2e/framework/workload_manager.go @@ -138,7 +138,12 @@ func (wm *WorkloadManager) TrafficManagerProfile() fleetnetv1alpha1.TrafficManag }, Spec: fleetnetv1alpha1.TrafficManagerProfileSpec{ MonitorConfig: &fleetnetv1alpha1.MonitorConfig{ - Port: ptr.To(int64(80)), + Port: ptr.To(int64(80)), + Protocol: ptr.To(fleetnetv1alpha1.TrafficManagerMonitorProtocolHTTPS), + Path: ptr.To("/path"), + IntervalInSeconds: ptr.To(int64(10)), + ToleratedNumberOfFailures: ptr.To(int64(3)), + TimeoutInSeconds: ptr.To(int64(8)), }, }, } diff --git a/test/e2e/traffic_manager_test.go b/test/e2e/traffic_manager_test.go index ff85a779..35672acf 100644 --- a/test/e2e/traffic_manager_test.go +++ b/test/e2e/traffic_manager_test.go @@ -5,14 +5,19 @@ Licensed under the MIT license. package e2e import ( + "fmt" "os" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/trafficmanager/armtrafficmanager" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" fleetnetv1alpha1 "go.goms.io/fleet-networking/api/v1alpha1" + "go.goms.io/fleet-networking/pkg/common/objectmeta" + "go.goms.io/fleet-networking/pkg/controllers/hub/trafficmanagerprofile" "go.goms.io/fleet-networking/test/common/trafficmanager/validator" "go.goms.io/fleet-networking/test/e2e/framework" ) @@ -21,7 +26,7 @@ var ( enabled = os.Getenv("ENABLE_TRAFFIC_MANAGER") == "true" ) -var _ = Describe("Test exporting service via Azure traffic manager", func() { +var _ = FDescribe("Test exporting service via Azure traffic manager", func() { var wm *framework.WorkloadManager var profile fleetnetv1alpha1.TrafficManagerProfile var hubClient client.Client @@ -43,7 +48,38 @@ var _ = Describe("Test exporting service via Azure traffic manager", func() { Expect(hubClient.Create(ctx, &profile)).Should(Succeed(), "Failed to creat the trafficManagerProfile") By("Validating the trafficManagerProfile status") - validator.ValidateIfTrafficManagerProfileIsProgrammed(ctx, hubClient, types.NamespacedName{Namespace: profile.Namespace, Name: profile.Name}) + wantDNSName := validator.ValidateIfTrafficManagerProfileIsProgrammed(ctx, hubClient, types.NamespacedName{Namespace: profile.Namespace, Name: profile.Name}) + + By("Validating the atm profile") + atmName := fmt.Sprintf(trafficmanagerprofile.AzureResourceProfileNameFormat, profile.UID) + monitorConfig := profile.Spec.MonitorConfig + namespacedName := types.NamespacedName{Name: profile.Name, Namespace: profile.Namespace} + want := armtrafficmanager.Profile{ + Location: ptr.To("global"), + Tags: map[string]*string{ + objectmeta.AzureTrafficManagerProfileTagKey: ptr.To(namespacedName.String()), + }, + Properties: &armtrafficmanager.ProfileProperties{ + ProfileStatus: ptr.To(armtrafficmanager.ProfileStatusEnabled), + TrafficRoutingMethod: ptr.To(armtrafficmanager.TrafficRoutingMethodWeighted), + DNSConfig: &armtrafficmanager.DNSConfig{ + RelativeName: ptr.To(fmt.Sprintf(trafficmanagerprofile.DNSRelativeNameFormat, profile.Namespace, profile.Name)), + Fqdn: &wantDNSName, + TTL: ptr.To(trafficmanagerprofile.DefaultDNSTTL), + }, + MonitorConfig: &armtrafficmanager.MonitorConfig{ + IntervalInSeconds: monitorConfig.IntervalInSeconds, + Path: monitorConfig.Path, + Port: monitorConfig.Port, + Protocol: ptr.To(armtrafficmanager.MonitorProtocol(*monitorConfig.Protocol)), + TimeoutInSeconds: monitorConfig.TimeoutInSeconds, + ToleratedNumberOfFailures: monitorConfig.ToleratedNumberOfFailures, + }, + Endpoints: []*armtrafficmanager.Endpoint{}, + TrafficViewEnrollmentStatus: ptr.To(armtrafficmanager.TrafficViewEnrollmentStatusDisabled), + }, + } + atmValidator.ValidateProfile(ctx, atmName, want) }) AfterEach(func() {