...

Source file src/edge-infra.dev/pkg/f8n/gcp/k8s/controllers/dennis/computeaddress_controller_test.go

Documentation: edge-infra.dev/pkg/f8n/gcp/k8s/controllers/dennis

     1  package dennis
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"os"
     8  	"testing"
     9  	"time"
    10  
    11  	compute "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/compute/v1beta1"
    12  	dns "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/dns/v1beta1"
    13  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/k8s/v1alpha1"
    14  	"github.com/stretchr/testify/suite"
    15  	v1 "k8s.io/api/core/v1"
    16  	"k8s.io/apimachinery/pkg/api/errors"
    17  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    18  	"k8s.io/apimachinery/pkg/types"
    19  	"k8s.io/apimachinery/pkg/util/yaml"
    20  
    21  	"edge-infra.dev/pkg/k8s/konfigkonnector/apis/meta"
    22  	"edge-infra.dev/pkg/k8s/runtime/controller"
    23  	"edge-infra.dev/test"
    24  	"edge-infra.dev/test/framework"
    25  	"edge-infra.dev/test/framework/gcp"
    26  	"edge-infra.dev/test/framework/integration"
    27  	"edge-infra.dev/test/framework/k8s"
    28  	"edge-infra.dev/test/framework/k8s/envtest"
    29  )
    30  
    31  func TestMain(m *testing.M) {
    32  	// register framework flags + parse them, must be done before actual test
    33  	// execution
    34  	framework.HandleFlags()
    35  	os.Exit(m.Run())
    36  }
    37  
    38  type Suite struct {
    39  	*framework.Framework
    40  	*k8s.K8s
    41  	ctx          context.Context
    42  	timeout      time.Duration
    43  	tick         time.Duration
    44  	projectID    string
    45  	dnsProjectID string
    46  	domain       string
    47  	zone         string
    48  }
    49  
    50  func TestDNSRecordController(t *testing.T) {
    51  	testEnv := envtest.Setup()
    52  	mgr, _, err := create(controller.WithCfg(testEnv.Config), controller.WithMetricsAddress("0"))
    53  	test.NoError(err)
    54  
    55  	k := k8s.New(testEnv.Config, k8s.WithCtrlManager(mgr), k8s.WithKonfigKonnector())
    56  
    57  	f := framework.New("dennis").
    58  		Component("dennis").
    59  		Register(k)
    60  
    61  	s := &Suite{
    62  		Framework:    f,
    63  		K8s:          k,
    64  		ctx:          context.Background(),
    65  		domain:       "my-domain.edge-infra.dev",
    66  		projectID:    "ret-edge-foo-fighters",
    67  		dnsProjectID: "ret-edge-dennys",
    68  		zone:         "edge-infra",
    69  	}
    70  
    71  	if integration.IsIntegrationTest() {
    72  		s.projectID = gcp.GCloud.ProjectID
    73  		s.dnsProjectID = gcp.GCloud.ProjectID
    74  		s.timeout = k8s.Timeouts.DefaultTimeout
    75  		s.tick = k8s.Timeouts.Tick
    76  	} else {
    77  		s.timeout = time.Second * 1
    78  		s.tick = time.Millisecond * 10
    79  	}
    80  
    81  	suite.Run(t, s)
    82  
    83  	t.Cleanup(func() {
    84  		f.NoError(testEnv.Stop())
    85  	})
    86  }
    87  
    88  func (s *Suite) TestDNSRecordCreation_SameNamespace() {
    89  	addy := s.createAddy("same-namespace", true)
    90  
    91  	s.NoError(s.Client.Create(s.ctx, addy))
    92  
    93  	d := &dns.DNSRecordSet{}
    94  	s.Eventually(func() bool {
    95  		err := s.Client.Get(s.ctx, types.NamespacedName{
    96  			Name:      addy.ObjectMeta.Name,
    97  			Namespace: s.Namespace,
    98  		}, d)
    99  		return !errors.IsNotFound(err)
   100  	}, s.timeout, s.tick, "expected DNSRecordSet was never created")
   101  	s.Equal(s.domain, d.Spec.Name)
   102  	s.Equal(s.zone, d.Spec.ManagedZoneRef.Name)
   103  	id, err := meta.GetProjectAnnotation(d.ObjectMeta)
   104  	s.NoError(err)
   105  	s.Equal(s.dnsProjectID, id)
   106  	s.Equal("", d.Spec.ManagedZoneRef.Namespace)
   107  }
   108  
   109  func (s *Suite) TestDNSRecordCreation_DifferentNamespace() {
   110  	zoneNS := "edge-infra-namespace"
   111  	zone := fmt.Sprintf("%s/%s", zoneNS, s.zone)
   112  	addy := s.createAddy("diff-namespace", true)
   113  	addy.ObjectMeta.Annotations[ManagedZoneAnnotation] = zone
   114  
   115  	s.NoError(s.Client.Create(s.ctx, addy))
   116  
   117  	d := &dns.DNSRecordSet{}
   118  	s.Eventually(func() bool {
   119  		err := s.Client.Get(s.ctx, types.NamespacedName{
   120  			Name:      addy.ObjectMeta.Name,
   121  			Namespace: s.Namespace,
   122  		}, d)
   123  		return !errors.IsNotFound(err)
   124  	}, s.timeout, s.tick, "expected DNSRecordSet was never created")
   125  	s.Equal(s.domain, d.Spec.Name)
   126  	s.Equal(s.zone, d.Spec.ManagedZoneRef.Name)
   127  	id, err := meta.GetProjectAnnotation(d.ObjectMeta)
   128  	s.NoError(err)
   129  	s.Equal(s.dnsProjectID, id)
   130  	s.Equal(zoneNS, d.Spec.ManagedZoneRef.Namespace)
   131  }
   132  
   133  func (s *Suite) TestDNSRecordNotCreated() {
   134  	addy := s.createAddy("no-record", false)
   135  	s.NoError(s.Client.Create(s.ctx, addy))
   136  	s.Never(func() bool {
   137  		d := &dns.DNSRecordSet{}
   138  		err := s.Client.Get(s.ctx, types.NamespacedName{
   139  			Name:      addy.ObjectMeta.Name,
   140  			Namespace: s.Namespace,
   141  		}, d)
   142  		return !errors.IsNotFound(err)
   143  	}, s.timeout, s.tick, "DNSRecordSet created when it shouldn't have been")
   144  }
   145  
   146  func (s *Suite) TestDNSRecordSetGarbageCollection() {
   147  	integration.SkipIfNot(s.Framework)
   148  	addy := s.createAddy("to-be-deleted", true)
   149  
   150  	s.NoError(s.Client.Create(s.ctx, addy))
   151  
   152  	s.Eventually(func() bool {
   153  		d := &dns.DNSRecordSet{}
   154  		err := s.Client.Get(s.ctx, types.NamespacedName{
   155  			Name:      addy.ObjectMeta.Name,
   156  			Namespace: s.Namespace,
   157  		}, d)
   158  		return !errors.IsNotFound(err)
   159  	}, s.timeout, s.tick, "expected DNSRecordSet was never created")
   160  
   161  	s.NoError(s.Client.Delete(s.ctx, addy))
   162  
   163  	s.Eventually(func() bool {
   164  		d := &dns.DNSRecordSet{}
   165  		err := s.Client.Get(s.ctx, types.NamespacedName{
   166  			Name:      addy.ObjectMeta.Name,
   167  			Namespace: s.Namespace,
   168  		}, d)
   169  		return errors.IsNotFound(err)
   170  	}, s.timeout, s.tick, "DNSRecordSet was never garbage collected")
   171  }
   172  
   173  func (s *Suite) TestDNSRecordSSA() {
   174  	addy := s.createAddy("server-side-apply", true)
   175  
   176  	s.NoError(s.Client.Create(s.ctx, addy))
   177  
   178  	d := &dns.DNSRecordSet{}
   179  	s.Eventually(func() bool {
   180  		err := s.Client.Get(s.ctx, types.NamespacedName{
   181  			Name:      addy.ObjectMeta.Name,
   182  			Namespace: s.Namespace,
   183  		}, d)
   184  		return !errors.IsNotFound(err)
   185  	}, s.timeout, s.tick, "expected DNSRecordSet was never created")
   186  	s.Equal(s.domain, d.Spec.Name)
   187  
   188  	s.Eventually(func() bool {
   189  		err := s.Client.Get(s.ctx, types.NamespacedName{
   190  			Name:      addy.ObjectMeta.Name,
   191  			Namespace: addy.ObjectMeta.Namespace,
   192  		}, addy)
   193  		return !errors.IsNotFound(err)
   194  	}, s.timeout, s.tick, "could not retrieve latest computeaddress")
   195  
   196  	testChangeDomain := "test.change.domain"
   197  	metav1.SetMetaDataAnnotation(&addy.ObjectMeta, NameAnnotation, testChangeDomain)
   198  	s.NoError(s.Client.Update(s.ctx, addy))
   199  
   200  	s.Eventually(func() bool {
   201  		err := s.Client.Get(s.ctx, types.NamespacedName{
   202  			Name:      addy.ObjectMeta.Name,
   203  			Namespace: s.Namespace,
   204  		}, d)
   205  		return !errors.IsNotFound(err) && d.ObjectMeta.Generation != 1
   206  	}, s.timeout, s.tick, "expected DNSRecordSet was never updated")
   207  	s.Equal(testChangeDomain, d.Spec.Name)
   208  }
   209  
   210  func (s *Suite) TestDNSRecordMigrateToNewProject() {
   211  	addy := s.createAddy("migrate-projects", true)
   212  	s.Require().NoError(s.Client.Create(s.ctx, addy))
   213  	d := &dns.DNSRecordSet{}
   214  	s.Eventually(func() bool {
   215  		err := s.Client.Get(s.ctx, types.NamespacedName{
   216  			Name:      addy.ObjectMeta.Name,
   217  			Namespace: s.Namespace,
   218  		}, d)
   219  		return !errors.IsNotFound(err)
   220  	}, s.timeout, s.tick, "expected DNSRecordSet was never created")
   221  	s.Equal(s.domain, d.Spec.Name)
   222  
   223  	s.Eventually(func() bool {
   224  		err := s.Client.Get(s.ctx, types.NamespacedName{
   225  			Name:      addy.ObjectMeta.Name,
   226  			Namespace: addy.ObjectMeta.Namespace,
   227  		}, addy)
   228  		return !errors.IsNotFound(err)
   229  	}, s.timeout, s.tick, "could not retrieve latest computeaddress")
   230  
   231  	testDuplicateToNewProject := "ret-edge-prod-dennys"
   232  	recordConfigs := []RecordConfig{
   233  		{
   234  			Name:         s.domain,
   235  			ManagedZone:  s.zone,
   236  			DNSProjectID: testDuplicateToNewProject,
   237  		},
   238  	}
   239  	recordConfigAnnoVal, _ := json.Marshal(recordConfigs)
   240  	metav1.SetMetaDataAnnotation(&addy.ObjectMeta, RecordConfigsAnnotation, string(recordConfigAnnoVal))
   241  	s.Require().NoError(s.Client.Update(s.ctx, addy))
   242  
   243  	outName := addy.ObjectMeta.Name + genSuffix(0)
   244  	s.Eventually(func() bool {
   245  		err := s.Client.Get(s.ctx, types.NamespacedName{
   246  			Name:      outName,
   247  			Namespace: s.Namespace,
   248  		}, d)
   249  		return !errors.IsNotFound(err)
   250  	}, s.timeout, s.tick, "expected DNSRecordSet was never created")
   251  	s.Equal(testDuplicateToNewProject, d.ObjectMeta.Annotations[meta.ProjectAnnotation])
   252  }
   253  
   254  func (s *Suite) TestDNSRecordFromRecordConfigs() {
   255  	addy := s.createAddy("from-record-configs", false)
   256  	id := "uniq."
   257  	// at least one part of project, zone, or domain must be unique
   258  	recordConfigs := []RecordConfig{
   259  		{
   260  			Name:         s.domain,
   261  			ManagedZone:  s.zone,
   262  			DNSProjectID: s.dnsProjectID,
   263  		},
   264  		{
   265  			Name:         id + s.domain,
   266  			ManagedZone:  s.zone,
   267  			DNSProjectID: s.dnsProjectID,
   268  		},
   269  		{
   270  			Name:         s.domain,
   271  			ManagedZone:  id + s.zone,
   272  			DNSProjectID: s.dnsProjectID,
   273  		},
   274  		{
   275  			Name:         s.domain,
   276  			ManagedZone:  s.zone,
   277  			DNSProjectID: id + s.dnsProjectID,
   278  		},
   279  	}
   280  	recordConfigAnnoVal, err := json.Marshal(recordConfigs)
   281  	s.Require().NoError(err)
   282  	metav1.SetMetaDataAnnotation(&addy.ObjectMeta, RecordConfigsAnnotation, string(recordConfigAnnoVal))
   283  	s.Require().NoError(s.Client.Create(s.ctx, addy))
   284  
   285  	for i := 0; i < 4; i++ {
   286  		drsName := fmt.Sprintf("%s%s", addy.ObjectMeta.Name, genSuffix(i))
   287  		d0 := &dns.DNSRecordSet{}
   288  		s.Require().Eventually(func() bool {
   289  			err := s.Client.Get(s.ctx, types.NamespacedName{
   290  				Name:      drsName,
   291  				Namespace: s.Namespace,
   292  			}, d0)
   293  			return !errors.IsNotFound(err)
   294  		}, s.timeout, s.tick, "expected DNSRecordSet was never created")
   295  	}
   296  }
   297  
   298  func (s *Suite) TestDNSRecordPreventDuplicates() {
   299  	addy := s.createAddy("prevent-duplicates", true)
   300  	recordConfigs := []RecordConfig{
   301  		{
   302  			Name:         s.domain,
   303  			ManagedZone:  s.zone,
   304  			DNSProjectID: s.dnsProjectID,
   305  		},
   306  	}
   307  	recordConfigAnnoVal, err := json.Marshal(recordConfigs)
   308  	s.NoError(err)
   309  	metav1.SetMetaDataAnnotation(&addy.ObjectMeta, RecordConfigsAnnotation, string(recordConfigAnnoVal))
   310  	s.NoError(s.Client.Create(s.ctx, addy))
   311  
   312  	// created by RecordConfig
   313  	outName0 := addy.ObjectMeta.Name + genSuffix(0)
   314  	d0 := &dns.DNSRecordSet{}
   315  	s.Never(func() bool {
   316  		err := s.Client.Get(s.ctx, types.NamespacedName{
   317  			Name:      outName0,
   318  			Namespace: s.Namespace,
   319  		}, d0)
   320  		return !errors.IsNotFound(err)
   321  	}, s.timeout, s.tick, "DNSRecordSet created when it shouldn't have been")
   322  
   323  	// created by singular annotations
   324  	d := &dns.DNSRecordSet{}
   325  	s.Never(func() bool {
   326  		err := s.Client.Get(s.ctx, types.NamespacedName{
   327  			Name:      addy.ObjectMeta.Name,
   328  			Namespace: s.Namespace,
   329  		}, d)
   330  		return !errors.IsNotFound(err)
   331  	}, s.timeout, s.tick, "DNSRecordSet created when it shouldn't have been")
   332  }
   333  
   334  func (s *Suite) TestDNSRecordConfigFromYaml() {
   335  	computeAddressYaml := []byte(fmt.Sprintf(`
   336  apiVersion: compute.cnrm.cloud.google.com/v1beta1
   337  kind: ComputeAddress
   338  metadata:
   339    name: edge-ingress-ip
   340    namespace: %s
   341    annotations:
   342      dns.edge.ncr.com/record-configs: |
   343        [
   344          {
   345            "name":"dev0.edge-preprod.dev.",
   346            "managed-zone":"infra/edge-preprod-dns-zone",
   347            "dns-project-id":"ret-edge-pltf-infra"
   348          },
   349          {
   350            "name":"dev0.edge-preprod.dev.",
   351            "managed-zone":"preprod-infra/edge-preprod-dns-managed-zone",
   352            "dns-project-id":"ret-edge-pltf-preprod-infra"
   353          }
   354        ]
   355  spec:
   356    location: global
   357    resourceID: edge-ingress-ip
   358  `, s.Namespace))
   359  
   360  	addy := &compute.ComputeAddress{}
   361  	s.Require().NoError(
   362  		yaml.Unmarshal(computeAddressYaml, addy),
   363  		"failed to unmarshal computeaddress from json document string",
   364  	)
   365  	ip := "192.158.1.20"
   366  	addy.Spec = compute.ComputeAddressSpec{Address: &ip}
   367  	addy.Status = compute.ComputeAddressStatus{
   368  		Conditions: []v1alpha1.Condition{
   369  			{Type: "Ready", Status: v1.ConditionTrue, Reason: "I said so"},
   370  		},
   371  	}
   372  	s.Require().NoError(s.Client.Create(s.ctx, addy))
   373  
   374  	// created by RecordConfig
   375  	outName0 := addy.ObjectMeta.Name + genSuffix(0)
   376  	outNamespace0 := addy.ObjectMeta.Namespace
   377  	d0 := &dns.DNSRecordSet{}
   378  	s.Require().Eventually(func() bool {
   379  		err := s.Client.Get(s.ctx, types.NamespacedName{
   380  			Name:      outName0,
   381  			Namespace: outNamespace0,
   382  		}, d0)
   383  		return !errors.IsNotFound(err)
   384  	}, s.timeout, s.tick, "expected DNSRecordSet was never created")
   385  }
   386  
   387  func (s *Suite) TestDeletionPolicyAnnoIsCopied() {
   388  	tname := "deletion-policy-is-copied"
   389  	dname := fmt.Sprintf("%s.%s", tname, s.domain)
   390  	addy := s.createAddy(tname, false)
   391  	metav1.SetMetaDataAnnotation(&addy.ObjectMeta, meta.DeletionPolicyAnnotation, meta.DeletionPolicyAbandon)
   392  	recordConfigs := []RecordConfig{
   393  		{
   394  			Name:         dname,
   395  			ManagedZone:  s.zone,
   396  			DNSProjectID: s.dnsProjectID,
   397  		},
   398  	}
   399  	recordConfigAnnoVal, err := json.Marshal(recordConfigs)
   400  	s.NoError(err)
   401  	metav1.SetMetaDataAnnotation(&addy.ObjectMeta, RecordConfigsAnnotation, string(recordConfigAnnoVal))
   402  	s.NoError(s.Client.Create(s.ctx, addy))
   403  
   404  	// expect deletion-policy to be copied from parent ComputeAddress
   405  	d := s.eventuallyGetRecordSet(addy.ObjectMeta.Name+genSuffix(0), s.Namespace)
   406  	s.Require().Equal(meta.DeletionPolicyAbandon, d.Annotations[meta.DeletionPolicyAnnotation])
   407  }
   408  
   409  func (s *Suite) TestDeletionPolicyAnnoEmpty() {
   410  	tname := "deletion-policy-is-empty"
   411  	dname := fmt.Sprintf("%s.%s", tname, s.domain)
   412  	addy := s.createAddy(tname, false)
   413  	recordConfigs := []RecordConfig{
   414  		{
   415  			Name:         dname,
   416  			ManagedZone:  s.zone,
   417  			DNSProjectID: s.dnsProjectID,
   418  		},
   419  	}
   420  	recordConfigAnnoVal, err := json.Marshal(recordConfigs)
   421  	s.NoError(err)
   422  	metav1.SetMetaDataAnnotation(&addy.ObjectMeta, RecordConfigsAnnotation, string(recordConfigAnnoVal))
   423  	s.NoError(s.Client.Create(s.ctx, addy))
   424  
   425  	// expect deletion-policy to be empty if not set on parent ComputeAddress
   426  	d := s.eventuallyGetRecordSet(addy.ObjectMeta.Name+genSuffix(0), s.Namespace)
   427  	s.Require().Equal("", d.Annotations[meta.DeletionPolicyAnnotation])
   428  }
   429  
   430  func (s *Suite) eventuallyGetRecordSet(name, namespace string) *dns.DNSRecordSet {
   431  	d := &dns.DNSRecordSet{}
   432  	s.Require().Eventually(func() bool {
   433  		err := s.Client.Get(s.ctx, types.NamespacedName{
   434  			Name:      name,
   435  			Namespace: namespace,
   436  		}, d)
   437  		return !errors.IsNotFound(err)
   438  	}, s.timeout, s.tick, "expected DNSRecordSet was never created")
   439  
   440  	return d
   441  }
   442  
   443  func (s *Suite) createAddy(name string, addAnnos bool) *compute.ComputeAddress {
   444  	addy := &compute.ComputeAddress{
   445  		ObjectMeta: metav1.ObjectMeta{
   446  			Name:      name,
   447  			Namespace: s.Namespace,
   448  		},
   449  		Spec: compute.ComputeAddressSpec{Location: "global"},
   450  	}
   451  	meta.SetProjectAnnotation(&addy.ObjectMeta, s.projectID)
   452  	if addAnnos {
   453  		metav1.SetMetaDataAnnotation(&addy.ObjectMeta, NameAnnotation, s.domain)
   454  		metav1.SetMetaDataAnnotation(&addy.ObjectMeta, ManagedZoneAnnotation, s.zone)
   455  		metav1.SetMetaDataAnnotation(&addy.ObjectMeta, DNSProjectAnnotation, s.dnsProjectID)
   456  	}
   457  
   458  	if !integration.IsIntegrationTest() {
   459  		ip := "192.158.1.20"
   460  		addy.Spec = compute.ComputeAddressSpec{Address: &ip}
   461  		addy.Status = compute.ComputeAddressStatus{
   462  			Conditions: []v1alpha1.Condition{
   463  				{Type: "Ready", Status: v1.ConditionTrue, Reason: "I said so"},
   464  			},
   465  		}
   466  	}
   467  
   468  	return addy
   469  }
   470  

View as plain text