Skip to content

Commit 1ee3995

Browse files
Merge pull request #1148 from chiragkyal/aws-tags
CFE-1134: Watch infrastructure and update AWS tags
2 parents 8bf1b36 + 19eced8 commit 1ee3995

File tree

7 files changed

+164
-10
lines changed

7 files changed

+164
-10
lines changed

pkg/operator/controller/ingress/controller.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ import (
4949

5050
const (
5151
controllerName = "ingress_controller"
52+
// clusterInfrastructureName is the name of the 'cluster' infrastructure object.
53+
clusterInfrastructureName = "cluster"
5254
)
5355

5456
// TODO: consider moving these to openshift/api
@@ -134,6 +136,12 @@ func New(mgr manager.Manager, config Config) (controller.Controller, error) {
134136
if err := c.Watch(source.Kind[client.Object](operatorCache, &configv1.Proxy{}, handler.EnqueueRequestsFromMapFunc(reconciler.ingressConfigToIngressController))); err != nil {
135137
return nil, err
136138
}
139+
// Watch for changes to infrastructure config to update user defined tags.
140+
if err := c.Watch(source.Kind[client.Object](operatorCache, &configv1.Infrastructure{}, handler.EnqueueRequestsFromMapFunc(reconciler.ingressConfigToIngressController),
141+
predicate.NewPredicateFuncs(hasName(clusterInfrastructureName)),
142+
)); err != nil {
143+
return nil, err
144+
}
137145
return c, nil
138146
}
139147

@@ -187,6 +195,13 @@ func enqueueRequestForOwningIngressController(namespace string) handler.EventHan
187195
})
188196
}
189197

198+
// hasName returns a predicate which checks whether an object has the given name.
199+
func hasName(name string) func(o client.Object) bool {
200+
return func(o client.Object) bool {
201+
return o.GetName() == name
202+
}
203+
}
204+
190205
// Config holds all the things necessary for the controller to run.
191206
type Config struct {
192207
Namespace string

pkg/operator/controller/ingress/load_balancer_service.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,11 @@ var (
254254
//
255255
// https://cloud.ibm.com/docs/containers?topic=containers-vpc-lbaas
256256
iksLBEnableFeaturesAnnotation,
257+
// awsLBAdditionalResourceTags annotation is populated
258+
// by user tags present in
259+
// Status.PlatformStatus.AWS.ResourceTags in the
260+
// infrastructure config.
261+
awsLBAdditionalResourceTags,
257262
)
258263

259264
// Azure and GCP support switching between internal and external
@@ -751,12 +756,6 @@ func IsServiceInternal(service *corev1.Service) bool {
751756
return false
752757
}
753758

754-
// loadBalancerServiceTagsModified verifies that none of the managedAnnotations have been changed and also the AWS tags annotation
755-
func loadBalancerServiceTagsModified(current, expected *corev1.Service) (bool, *corev1.Service) {
756-
ignoredAnnotations := managedLoadBalancerServiceAnnotations.Union(sets.NewString(awsLBAdditionalResourceTags))
757-
return loadBalancerServiceAnnotationsChanged(current, expected, ignoredAnnotations)
758-
}
759-
760759
// loadBalancerServiceIsUpgradeable returns an error value indicating if the
761760
// load balancer service is safe to upgrade. In particular, if the current
762761
// service matches the desired service, then the service is upgradeable, and the
@@ -773,7 +772,11 @@ func loadBalancerServiceIsUpgradeable(ic *operatorv1.IngressController, deployme
773772
return nil
774773
}
775774

776-
changed, updated := loadBalancerServiceTagsModified(current, desired)
775+
// Verify that none of the managedAnnotations have been changed by something or someone.
776+
// Since the status logic runs after the controller sets the annotations, it checks for
777+
// any discrepancy (in case modified) between the desired annotation values of the controller
778+
// and the current annotation values.
779+
changed, updated := loadBalancerServiceAnnotationsChanged(current, desired, managedLoadBalancerServiceAnnotations)
777780
if changed {
778781
diff := cmp.Diff(current, updated, cmpopts.EquateEmpty())
779782
return fmt.Errorf("load balancer service has been modified; changes must be reverted before upgrading: %s", diff)

pkg/operator/controller/ingress/load_balancer_service_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1141,7 +1141,7 @@ func Test_loadBalancerServiceChanged(t *testing.T) {
11411141
mutate: func(svc *corev1.Service) {
11421142
svc.Annotations["service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags"] = "Key3=Value3,Key4=Value4"
11431143
},
1144-
expect: false,
1144+
expect: true,
11451145
},
11461146
{
11471147
description: "if the service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout annotation changes",

pkg/operator/controller/ingress/status_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3022,7 +3022,7 @@ func Test_computeIngressUpgradeableCondition(t *testing.T) {
30223022
expect: true,
30233023
},
30243024
{
3025-
description: "if the service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags annotation changes",
3025+
description: "if the service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags annotation changes not by ingress controller",
30263026
mutate: func(svc *corev1.Service) {
30273027
svc.Annotations[awsLBAdditionalResourceTags] = "Key2=Value2"
30283028
},

test/e2e/all_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ func TestAll(t *testing.T) {
100100
t.Run("TestDefaultIngressClass", TestDefaultIngressClass)
101101
t.Run("TestOperatorRecreatesItsClusterOperator", TestOperatorRecreatesItsClusterOperator)
102102
t.Run("TestAWSLBTypeDefaulting", TestAWSLBTypeDefaulting)
103+
t.Run("TestAWSResourceTagsChanged", TestAWSResourceTagsChanged)
103104
t.Run("TestHstsPolicyWorks", TestHstsPolicyWorks)
104105
t.Run("TestIngressControllerCustomEndpoints", TestIngressControllerCustomEndpoints)
105106
t.Run("TestIngressStatus", TestIngressStatus)

test/e2e/operator_test.go

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"encoding/json"
1313
"errors"
1414
"fmt"
15-
"github.com/aws/aws-sdk-go/service/ec2"
1615
"io"
1716
"io/ioutil"
1817
"net"
@@ -32,13 +31,15 @@ import (
3231
iov1 "github.com/openshift/api/operatoringress/v1"
3332
routev1 "github.com/openshift/api/route/v1"
3433

34+
configclientset "github.com/openshift/client-go/config/clientset/versioned"
3535
"github.com/openshift/cluster-ingress-operator/pkg/manifests"
3636
operatorclient "github.com/openshift/cluster-ingress-operator/pkg/operator/client"
3737
"github.com/openshift/cluster-ingress-operator/pkg/operator/controller"
3838
operatorcontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller"
3939
ingresscontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller/ingress"
4040

4141
"github.com/aws/aws-sdk-go/aws/endpoints"
42+
"github.com/aws/aws-sdk-go/service/ec2"
4243

4344
"github.com/go-logr/logr"
4445
"github.com/stretchr/testify/assert"
@@ -116,6 +117,7 @@ var (
116117

117118
var (
118119
kclient client.Client
120+
configClient *configclientset.Clientset
119121
dnsConfig configv1.DNS
120122
infraConfig configv1.Infrastructure
121123
operatorNamespace = operatorcontroller.DefaultOperatorNamespace
@@ -153,6 +155,12 @@ func TestMain(m *testing.M) {
153155
}
154156
kclient = kubeClient
155157

158+
configClient, err = configclientset.NewForConfig(kubeConfig)
159+
if err != nil {
160+
fmt.Printf("failed to create config client: %s\n", err)
161+
os.Exit(1)
162+
}
163+
156164
if err := kclient.Get(context.TODO(), types.NamespacedName{Name: "cluster"}, &dnsConfig); err != nil {
157165
fmt.Printf("failed to get DNS config: %v\n", err)
158166
os.Exit(1)
@@ -1285,6 +1293,83 @@ func TestInternalLoadBalancerGlobalAccessGCP(t *testing.T) {
12851293
}
12861294
}
12871295

1296+
// TestAWSResourceTagsChanged tests the functionality of updating AWS resource tags
1297+
// in the infrastructure configuration and validates that the expected
1298+
// awsLBAdditionalResourceTags is set correctly on the
1299+
// loadBalancer service associated with the default Ingress Controller.
1300+
//
1301+
// This test is a serial test because it modifies the cluster infrastructure config and
1302+
// therefore should not run in parallel with other tests.
1303+
func TestAWSResourceTagsChanged(t *testing.T) {
1304+
if infraConfig.Status.Platform != "AWS" {
1305+
t.Skipf("test skipped on platform %q", infraConfig.Status.Platform)
1306+
}
1307+
if err := waitForIngressControllerCondition(t, kclient, 10*time.Second, defaultName, defaultAvailableConditions...); err != nil {
1308+
t.Errorf("did not get expected conditions: %v", err)
1309+
}
1310+
defaultIC := &operatorv1.IngressController{
1311+
ObjectMeta: metav1.ObjectMeta{
1312+
Namespace: defaultName.Namespace,
1313+
Name: defaultName.Name,
1314+
},
1315+
}
1316+
awsLBAdditionalResourceTags := "service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags"
1317+
1318+
// Save a copy of the original infraConfig, to revert changes before exiting.
1319+
originalInfra := infraConfig.DeepCopy()
1320+
t.Cleanup(func() {
1321+
err := updateInfrastructureConfigStatusWithRetryOnConflict(t, 5*time.Minute, configClient, func(infra *configv1.Infrastructure) *configv1.Infrastructure {
1322+
infra.Status = originalInfra.Status
1323+
return infra
1324+
})
1325+
if err != nil {
1326+
t.Logf("Unable to remove changes from the infraConfig, possible corruption of test environment: %v", err)
1327+
}
1328+
})
1329+
1330+
initialTags := []configv1.AWSResourceTag{
1331+
{Key: "Key1", Value: "Value1"},
1332+
{Key: "Key2", Value: "Value2"},
1333+
}
1334+
t.Logf("Updating AWS ResourceTags in the cluster infrastructure config: %v", initialTags)
1335+
err := updateInfrastructureConfigStatusWithRetryOnConflict(t, 5*time.Minute, configClient, func(infra *configv1.Infrastructure) *configv1.Infrastructure {
1336+
if infra.Status.PlatformStatus == nil {
1337+
infra.Status.PlatformStatus = &configv1.PlatformStatus{}
1338+
}
1339+
if infra.Status.PlatformStatus.AWS == nil {
1340+
infra.Status.PlatformStatus.AWS = &configv1.AWSPlatformStatus{}
1341+
}
1342+
infra.Status.PlatformStatus.AWS.ResourceTags = initialTags
1343+
return infra
1344+
})
1345+
if err != nil {
1346+
t.Errorf("failed to update infrastructure status: %v", err)
1347+
}
1348+
1349+
// Check awsLBAdditionalResourceTags annotation with initial tags.
1350+
expectedTags := "Key1=Value1,Key2=Value2"
1351+
t.Logf("Validating the %s annotation for the load balancer service of the default ingresscontroller", awsLBAdditionalResourceTags)
1352+
assertLoadBalancerServiceAnnotationWithPollImmediate(t, kclient, defaultIC, awsLBAdditionalResourceTags, expectedTags)
1353+
1354+
// Update the status again, removing one tag.
1355+
updatedTags := []configv1.AWSResourceTag{
1356+
{Key: "Key1", Value: "Value1"},
1357+
}
1358+
t.Logf("Updating AWS ResourceTags in the cluster infrastructure config: %v", updatedTags)
1359+
err = updateInfrastructureConfigStatusWithRetryOnConflict(t, 5*time.Minute, configClient, func(infra *configv1.Infrastructure) *configv1.Infrastructure {
1360+
infra.Status.PlatformStatus.AWS.ResourceTags = updatedTags
1361+
return infra
1362+
})
1363+
if err != nil {
1364+
t.Errorf("failed to update infrastructure status: %v", err)
1365+
}
1366+
1367+
// Check awsLBAdditionalResourceTags annotation with updated tags.
1368+
expectedTags = "Key1=Value1"
1369+
t.Logf("Validating the %s annotation for the load balancer service of the default ingresscontroller", awsLBAdditionalResourceTags)
1370+
assertLoadBalancerServiceAnnotationWithPollImmediate(t, kclient, defaultIC, awsLBAdditionalResourceTags, expectedTags)
1371+
}
1372+
12881373
func TestAWSLBTypeChange(t *testing.T) {
12891374
t.Parallel()
12901375

@@ -4287,6 +4372,29 @@ func assertServiceAnnotation(t *testing.T, serviceName types.NamespacedName, ann
42874372
}
42884373
}
42894374

4375+
// assertLoadBalancerServiceAnnotationWithPollImmediate checks if the specified annotation on the
4376+
// LoadBalancer Service of the given IngressController matches the expected value.
4377+
func assertLoadBalancerServiceAnnotationWithPollImmediate(t *testing.T, kclient client.Client, ic *operatorv1.IngressController, annotationKey, expectedValue string) {
4378+
err := wait.PollUntilContextTimeout(context.Background(), 5*time.Second, 5*time.Minute, true, func(ctx context.Context) (bool, error) {
4379+
service := &corev1.Service{}
4380+
if err := kclient.Get(ctx, controller.LoadBalancerServiceName(ic), service); err != nil {
4381+
t.Logf("failed to get service %s: %v, retrying...", controller.LoadBalancerServiceName(ic), err)
4382+
return false, nil
4383+
}
4384+
if actualValue, ok := service.Annotations[annotationKey]; !ok {
4385+
t.Logf("load balancer has no %q annotation yet: %v, retrying...", annotationKey, service.Annotations)
4386+
return false, nil
4387+
} else if actualValue != expectedValue {
4388+
t.Logf("expected %s, found %s", expectedValue, actualValue)
4389+
return false, nil
4390+
}
4391+
return true, nil
4392+
})
4393+
if err != nil {
4394+
t.Fatalf("timed out waiting for the %s annotation to be updated: %v", annotationKey, err)
4395+
}
4396+
}
4397+
42904398
// assertServiceNotDeleted asserts that a provide service wasn't deleted.
42914399
func assertServiceNotDeleted(t *testing.T, serviceName types.NamespacedName, oldUid types.UID) {
42924400
t.Helper()

test/e2e/util_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
configv1 "github.com/openshift/api/config/v1"
1818
operatorv1 "github.com/openshift/api/operator/v1"
1919
routev1 "github.com/openshift/api/route/v1"
20+
configclientset "github.com/openshift/client-go/config/clientset/versioned"
2021
"github.com/openshift/cluster-ingress-operator/pkg/operator/controller"
2122

2223
appsv1 "k8s.io/api/apps/v1"
@@ -621,6 +622,32 @@ func updateInfrastructureConfigSpecWithRetryOnConflict(t *testing.T, name types.
621622
})
622623
}
623624

625+
// updateInfrastructureStatus updates the Infrastructure status by applying
626+
// the given update function to the current Infrastructure object.
627+
// If there is a conflict error on update then the complete operation
628+
// is retried until timeout is reached.
629+
func updateInfrastructureConfigStatusWithRetryOnConflict(t *testing.T, timeout time.Duration, configClient *configclientset.Clientset, updateFunc func(*configv1.Infrastructure) *configv1.Infrastructure) error {
630+
return wait.PollUntilContextTimeout(context.Background(), 5*time.Second, timeout, true, func(ctx context.Context) (bool, error) {
631+
infra, err := configClient.ConfigV1().Infrastructures().Get(ctx, "cluster", metav1.GetOptions{})
632+
if err != nil {
633+
t.Logf("error getting 'cluster' infrastructure config: %v, retrying...", err)
634+
return false, nil
635+
}
636+
637+
// Apply the update function to the Infrastructure object.
638+
updatedInfra := updateFunc(infra.DeepCopy())
639+
640+
if _, err := configClient.ConfigV1().Infrastructures().UpdateStatus(ctx, updatedInfra, metav1.UpdateOptions{}); err != nil {
641+
if errors.IsConflict(err) {
642+
t.Logf("conflict when updating 'cluster' infrastructure config: %v, retrying...", err)
643+
return false, nil
644+
}
645+
return false, err
646+
}
647+
return true, nil
648+
})
649+
}
650+
624651
// verifyExternalIngressController verifies connectivity between the router
625652
// and a test workload by making a http call using the hostname passed to it.
626653
// This hostname must be the domain associated with the ingresscontroller under test.

0 commit comments

Comments
 (0)