sigs.k8s.io/external-dns@v0.14.1/provider/designate/designate_test.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package designate
    18  
    19  import (
    20  	"context"
    21  	"encoding/pem"
    22  	"fmt"
    23  	"net/http"
    24  	"net/http/httptest"
    25  	"os"
    26  	"reflect"
    27  	"sort"
    28  	"sync/atomic"
    29  	"testing"
    30  
    31  	"github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets"
    32  	"github.com/gophercloud/gophercloud/openstack/dns/v2/zones"
    33  
    34  	"sigs.k8s.io/external-dns/endpoint"
    35  	"sigs.k8s.io/external-dns/plan"
    36  	"sigs.k8s.io/external-dns/provider"
    37  )
    38  
    39  var lastGeneratedDesignateID int32
    40  
    41  func generateDesignateID() string {
    42  	return fmt.Sprintf("id-%d", atomic.AddInt32(&lastGeneratedDesignateID, 1))
    43  }
    44  
    45  type fakeDesignateClient struct {
    46  	managedZones map[string]*struct {
    47  		zone       *zones.Zone
    48  		recordSets map[string]*recordsets.RecordSet
    49  	}
    50  }
    51  
    52  func (c fakeDesignateClient) AddZone(zone zones.Zone) string {
    53  	if zone.ID == "" {
    54  		zone.ID = zone.Name
    55  	}
    56  	c.managedZones[zone.ID] = &struct {
    57  		zone       *zones.Zone
    58  		recordSets map[string]*recordsets.RecordSet
    59  	}{
    60  		zone:       &zone,
    61  		recordSets: make(map[string]*recordsets.RecordSet),
    62  	}
    63  	return zone.ID
    64  }
    65  
    66  func (c fakeDesignateClient) ForEachZone(handler func(zone *zones.Zone) error) error {
    67  	for _, zone := range c.managedZones {
    68  		if err := handler(zone.zone); err != nil {
    69  			return err
    70  		}
    71  	}
    72  	return nil
    73  }
    74  
    75  func (c fakeDesignateClient) ForEachRecordSet(zoneID string, handler func(recordSet *recordsets.RecordSet) error) error {
    76  	zone := c.managedZones[zoneID]
    77  	if zone == nil {
    78  		return fmt.Errorf("unknown zone %s", zoneID)
    79  	}
    80  	for _, recordSet := range zone.recordSets {
    81  		if err := handler(recordSet); err != nil {
    82  			return err
    83  		}
    84  	}
    85  	return nil
    86  }
    87  
    88  func (c fakeDesignateClient) CreateRecordSet(zoneID string, opts recordsets.CreateOpts) (string, error) {
    89  	zone := c.managedZones[zoneID]
    90  	if zone == nil {
    91  		return "", fmt.Errorf("unknown zone %s", zoneID)
    92  	}
    93  	rs := &recordsets.RecordSet{
    94  		ID:          generateDesignateID(),
    95  		ZoneID:      zoneID,
    96  		Name:        opts.Name,
    97  		Description: opts.Description,
    98  		Records:     opts.Records,
    99  		TTL:         opts.TTL,
   100  		Type:        opts.Type,
   101  	}
   102  	zone.recordSets[rs.ID] = rs
   103  	return rs.ID, nil
   104  }
   105  
   106  func (c fakeDesignateClient) UpdateRecordSet(zoneID, recordSetID string, opts recordsets.UpdateOpts) error {
   107  	zone := c.managedZones[zoneID]
   108  	if zone == nil {
   109  		return fmt.Errorf("unknown zone %s", zoneID)
   110  	}
   111  	rs := zone.recordSets[recordSetID]
   112  	if rs == nil {
   113  		return fmt.Errorf("unknown record-set %s", recordSetID)
   114  	}
   115  	if opts.Description != nil {
   116  		rs.Description = *opts.Description
   117  	}
   118  	rs.TTL = *opts.TTL
   119  
   120  	rs.Records = opts.Records
   121  	return nil
   122  }
   123  
   124  func (c fakeDesignateClient) DeleteRecordSet(zoneID, recordSetID string) error {
   125  	zone := c.managedZones[zoneID]
   126  	if zone == nil {
   127  		return fmt.Errorf("unknown zone %s", zoneID)
   128  	}
   129  	delete(zone.recordSets, recordSetID)
   130  	return nil
   131  }
   132  
   133  func (c fakeDesignateClient) ToProvider() provider.Provider {
   134  	return &designateProvider{client: c}
   135  }
   136  
   137  func newFakeDesignateClient() *fakeDesignateClient {
   138  	return &fakeDesignateClient{
   139  		make(map[string]*struct {
   140  			zone       *zones.Zone
   141  			recordSets map[string]*recordsets.RecordSet
   142  		}),
   143  	}
   144  }
   145  
   146  func TestNewDesignateProvider(t *testing.T) {
   147  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   148  		w.WriteHeader(http.StatusAccepted)
   149  		w.Write([]byte(`{
   150  		  "token": {
   151  		    "catalog": [
   152  		      {
   153  		        "id": "9615c2dfac3b4b19935226d4c9d4afce",
   154  		        "name": "designate",
   155  		        "type": "dns",
   156  		        "endpoints": [
   157  		          {
   158  		            "id": "3d3cc3a273b54d0490ac43d6572e4c48",
   159  		            "region": "RegionOne",
   160  		            "region_id": "RegionOne",
   161  		            "interface": "public",
   162  		            "url": "https://example.com:9001"
   163  		          }
   164  		        ]
   165  		      }
   166  		    ]
   167  		  }
   168  		}`))
   169  	}))
   170  	defer ts.Close()
   171  
   172  	block := &pem.Block{
   173  		Type:  "CERTIFICATE",
   174  		Bytes: ts.Certificate().Raw,
   175  	}
   176  	tmpfile, err := os.CreateTemp("", "os-test.crt")
   177  	if err != nil {
   178  		t.Fatal(err)
   179  	}
   180  	defer os.Remove(tmpfile.Name())
   181  	if err := pem.Encode(tmpfile, block); err != nil {
   182  		t.Fatal(err)
   183  	}
   184  	if err := tmpfile.Close(); err != nil {
   185  		t.Fatal(err)
   186  	}
   187  
   188  	os.Setenv("OS_AUTH_URL", ts.URL+"/v3")
   189  	os.Setenv("OS_USERNAME", "username")
   190  	os.Setenv("OS_PASSWORD", "password")
   191  	os.Setenv("OS_USER_DOMAIN_NAME", "Default")
   192  	os.Setenv("OPENSTACK_CA_FILE", tmpfile.Name())
   193  
   194  	if _, err := NewDesignateProvider(endpoint.DomainFilter{}, true); err != nil {
   195  		t.Fatalf("Failed to initialize Designate provider: %s", err)
   196  	}
   197  }
   198  
   199  func TestDesignateRecords(t *testing.T) {
   200  	client := newFakeDesignateClient()
   201  
   202  	zone1ID := client.AddZone(zones.Zone{
   203  		Name:   "example.com.",
   204  		Type:   "PRIMARY",
   205  		Status: "ACTIVE",
   206  	})
   207  	rs11ID, _ := client.CreateRecordSet(zone1ID, recordsets.CreateOpts{
   208  		Name:    "www.example.com.",
   209  		Type:    endpoint.RecordTypeA,
   210  		Records: []string{"10.1.1.1"},
   211  	})
   212  	rs12ID, _ := client.CreateRecordSet(zone1ID, recordsets.CreateOpts{
   213  		Name:    "www.example.com.",
   214  		Type:    endpoint.RecordTypeTXT,
   215  		Records: []string{"text1"},
   216  	})
   217  	client.CreateRecordSet(zone1ID, recordsets.CreateOpts{
   218  		Name:    "xxx.example.com.",
   219  		Type:    "SRV",
   220  		Records: []string{"http://test.com:1234"},
   221  	})
   222  	rs14ID, _ := client.CreateRecordSet(zone1ID, recordsets.CreateOpts{
   223  		Name:    "ftp.example.com.",
   224  		Type:    endpoint.RecordTypeA,
   225  		Records: []string{"10.1.1.2"},
   226  	})
   227  
   228  	zone2ID := client.AddZone(zones.Zone{
   229  		Name:   "test.net.",
   230  		Type:   "PRIMARY",
   231  		Status: "ACTIVE",
   232  	})
   233  	rs21ID, _ := client.CreateRecordSet(zone2ID, recordsets.CreateOpts{
   234  		Name:    "srv.test.net.",
   235  		Type:    endpoint.RecordTypeA,
   236  		Records: []string{"10.2.1.1", "10.2.1.2"},
   237  	})
   238  	rs22ID, _ := client.CreateRecordSet(zone2ID, recordsets.CreateOpts{
   239  		Name:    "db.test.net.",
   240  		Type:    endpoint.RecordTypeCNAME,
   241  		Records: []string{"sql.test.net."},
   242  	})
   243  	expected := []*endpoint.Endpoint{
   244  		{
   245  			DNSName:    "www.example.com",
   246  			RecordType: endpoint.RecordTypeA,
   247  			Targets:    endpoint.Targets{"10.1.1.1"},
   248  			Labels: map[string]string{
   249  				designateRecordSetID:     rs11ID,
   250  				designateZoneID:          zone1ID,
   251  				designateOriginalRecords: "10.1.1.1",
   252  			},
   253  		},
   254  		{
   255  			DNSName:    "www.example.com",
   256  			RecordType: endpoint.RecordTypeTXT,
   257  			Targets:    endpoint.Targets{"text1"},
   258  			Labels: map[string]string{
   259  				designateRecordSetID:     rs12ID,
   260  				designateZoneID:          zone1ID,
   261  				designateOriginalRecords: "text1",
   262  			},
   263  		},
   264  		{
   265  			DNSName:    "ftp.example.com",
   266  			RecordType: endpoint.RecordTypeA,
   267  			Targets:    endpoint.Targets{"10.1.1.2"},
   268  			Labels: map[string]string{
   269  				designateRecordSetID:     rs14ID,
   270  				designateZoneID:          zone1ID,
   271  				designateOriginalRecords: "10.1.1.2",
   272  			},
   273  		},
   274  		{
   275  			DNSName:    "srv.test.net",
   276  			RecordType: endpoint.RecordTypeA,
   277  			Targets:    endpoint.Targets{"10.2.1.1", "10.2.1.2"},
   278  			Labels: map[string]string{
   279  				designateRecordSetID:     rs21ID,
   280  				designateZoneID:          zone2ID,
   281  				designateOriginalRecords: "10.2.1.1\00010.2.1.2",
   282  			},
   283  		},
   284  		{
   285  			DNSName:    "db.test.net",
   286  			RecordType: endpoint.RecordTypeCNAME,
   287  			Targets:    endpoint.Targets{"sql.test.net"},
   288  			Labels: map[string]string{
   289  				designateRecordSetID:     rs22ID,
   290  				designateZoneID:          zone2ID,
   291  				designateOriginalRecords: "sql.test.net.",
   292  			},
   293  		},
   294  	}
   295  
   296  	endpoints, err := client.ToProvider().Records(context.Background())
   297  	if err != nil {
   298  		t.Fatal(err)
   299  	}
   300  out:
   301  	for _, ep := range endpoints {
   302  		for i, ex := range expected {
   303  			if reflect.DeepEqual(ep, ex) {
   304  				expected = append(expected[:i], expected[i+1:]...)
   305  				continue out
   306  			}
   307  		}
   308  		t.Errorf("unexpected endpoint %s/%s -> %s", ep.DNSName, ep.RecordType, ep.Targets)
   309  	}
   310  	if len(expected) != 0 {
   311  		t.Errorf("not all expected endpoints were returned. Remained: %v", expected)
   312  	}
   313  }
   314  
   315  func TestDesignateCreateRecords(t *testing.T) {
   316  	client := newFakeDesignateClient()
   317  	testDesignateCreateRecords(t, client)
   318  }
   319  
   320  func testDesignateCreateRecords(t *testing.T, client *fakeDesignateClient) []*recordsets.RecordSet {
   321  	for i, zoneName := range []string{"example.com.", "test.net."} {
   322  		client.AddZone(zones.Zone{
   323  			ID:     fmt.Sprintf("zone-%d", i+1),
   324  			Name:   zoneName,
   325  			Type:   "PRIMARY",
   326  			Status: "ACTIVE",
   327  		})
   328  	}
   329  
   330  	_, err := client.CreateRecordSet("zone-1", recordsets.CreateOpts{
   331  		Name:        "www.example.com.",
   332  		Description: "",
   333  		Records:     []string{"foo"},
   334  		TTL:         60,
   335  		Type:        endpoint.RecordTypeTXT,
   336  	})
   337  
   338  	if err != nil {
   339  		t.Fatal("failed to prefil records")
   340  	}
   341  
   342  	endpoints := []*endpoint.Endpoint{
   343  		{
   344  			DNSName:    "www.example.com",
   345  			RecordType: endpoint.RecordTypeA,
   346  			Targets:    endpoint.Targets{"10.1.1.1"},
   347  			Labels:     map[string]string{},
   348  		},
   349  		{
   350  			DNSName:    "www.example.com",
   351  			RecordType: endpoint.RecordTypeTXT,
   352  			Targets:    endpoint.Targets{"text1"},
   353  			Labels:     map[string]string{},
   354  		},
   355  		{
   356  			DNSName:    "ftp.example.com",
   357  			RecordType: endpoint.RecordTypeA,
   358  			Targets:    endpoint.Targets{"10.1.1.2"},
   359  			Labels:     map[string]string{},
   360  		},
   361  		{
   362  			DNSName:    "srv.test.net",
   363  			RecordType: endpoint.RecordTypeA,
   364  			Targets:    endpoint.Targets{"10.2.1.1"},
   365  			Labels:     map[string]string{},
   366  		},
   367  		{
   368  			DNSName:    "srv.test.net",
   369  			RecordType: endpoint.RecordTypeA,
   370  			Targets:    endpoint.Targets{"10.2.1.2"},
   371  			Labels:     map[string]string{},
   372  		},
   373  		{
   374  			DNSName:    "db.test.net",
   375  			RecordType: endpoint.RecordTypeCNAME,
   376  			Targets:    endpoint.Targets{"sql.test.net"},
   377  			Labels:     map[string]string{},
   378  		},
   379  	}
   380  	expected := []*recordsets.RecordSet{
   381  		{
   382  			Name:    "www.example.com.",
   383  			Type:    endpoint.RecordTypeA,
   384  			Records: []string{"10.1.1.1"},
   385  			ZoneID:  "zone-1",
   386  		},
   387  		{
   388  			Name:    "www.example.com.",
   389  			Type:    endpoint.RecordTypeTXT,
   390  			Records: []string{"text1"},
   391  			ZoneID:  "zone-1",
   392  		},
   393  		{
   394  			Name:    "ftp.example.com.",
   395  			Type:    endpoint.RecordTypeA,
   396  			Records: []string{"10.1.1.2"},
   397  			ZoneID:  "zone-1",
   398  		},
   399  		{
   400  			Name:    "srv.test.net.",
   401  			Type:    endpoint.RecordTypeA,
   402  			Records: []string{"10.2.1.1", "10.2.1.2"},
   403  			ZoneID:  "zone-2",
   404  		},
   405  		{
   406  			Name:    "db.test.net.",
   407  			Type:    endpoint.RecordTypeCNAME,
   408  			Records: []string{"sql.test.net."},
   409  			ZoneID:  "zone-2",
   410  		},
   411  	}
   412  	expectedCopy := make([]*recordsets.RecordSet, len(expected))
   413  	copy(expectedCopy, expected)
   414  
   415  	err = client.ToProvider().ApplyChanges(context.Background(), &plan.Changes{Create: endpoints})
   416  	if err != nil {
   417  		t.Fatal(err)
   418  	}
   419  
   420  	client.ForEachZone(func(zone *zones.Zone) error {
   421  		client.ForEachRecordSet(zone.ID, func(recordSet *recordsets.RecordSet) error {
   422  			id := recordSet.ID
   423  			recordSet.ID = ""
   424  			for i, ex := range expected {
   425  				sort.Strings(recordSet.Records)
   426  				if reflect.DeepEqual(ex, recordSet) {
   427  					ex.ID = id
   428  					recordSet.ID = id
   429  					expected = append(expected[:i], expected[i+1:]...)
   430  					return nil
   431  				}
   432  			}
   433  			t.Errorf("unexpected record-set %s/%s -> %v", recordSet.Name, recordSet.Type, recordSet.Records)
   434  			return nil
   435  		})
   436  		return nil
   437  	})
   438  
   439  	if len(expected) != 0 {
   440  		t.Errorf("not all expected record-sets were created. Remained: %v", expected)
   441  	}
   442  	return expectedCopy
   443  }
   444  
   445  func TestDesignateUpdateRecords(t *testing.T) {
   446  	client := newFakeDesignateClient()
   447  	testDesignateUpdateRecords(t, client)
   448  }
   449  
   450  func testDesignateUpdateRecords(t *testing.T, client *fakeDesignateClient) []*recordsets.RecordSet {
   451  	expected := testDesignateCreateRecords(t, client)
   452  
   453  	updatesOld := []*endpoint.Endpoint{
   454  		{
   455  			DNSName:    "ftp.example.com",
   456  			RecordType: endpoint.RecordTypeA,
   457  			Targets:    endpoint.Targets{"10.1.1.2"},
   458  			Labels: map[string]string{
   459  				designateZoneID:          "zone-1",
   460  				designateRecordSetID:     expected[2].ID,
   461  				designateOriginalRecords: "10.1.1.2",
   462  			},
   463  		},
   464  		{
   465  			DNSName:    "srv.test.net.",
   466  			RecordType: endpoint.RecordTypeA,
   467  			Targets:    endpoint.Targets{"10.2.1.2"},
   468  			Labels: map[string]string{
   469  				designateZoneID:          "zone-2",
   470  				designateRecordSetID:     expected[3].ID,
   471  				designateOriginalRecords: "10.2.1.1\00010.2.1.2",
   472  			},
   473  		},
   474  	}
   475  	updatesNew := []*endpoint.Endpoint{
   476  		{
   477  			DNSName:    "ftp.example.com",
   478  			RecordType: endpoint.RecordTypeA,
   479  			Targets:    endpoint.Targets{"10.3.3.1"},
   480  			Labels: map[string]string{
   481  				designateZoneID:          "zone-1",
   482  				designateRecordSetID:     expected[2].ID,
   483  				designateOriginalRecords: "10.1.1.2",
   484  			},
   485  		},
   486  		{
   487  			DNSName:    "srv.test.net.",
   488  			RecordType: endpoint.RecordTypeA,
   489  			Targets:    endpoint.Targets{"10.3.3.2"},
   490  			Labels: map[string]string{
   491  				designateZoneID:          "zone-2",
   492  				designateRecordSetID:     expected[3].ID,
   493  				designateOriginalRecords: "10.2.1.1\00010.2.1.2",
   494  			},
   495  		},
   496  	}
   497  	expectedCopy := make([]*recordsets.RecordSet, len(expected))
   498  	copy(expectedCopy, expected)
   499  
   500  	expected[2].Records = []string{"10.3.3.1"}
   501  	expected[3].Records = []string{"10.2.1.1", "10.3.3.2"}
   502  
   503  	err := client.ToProvider().ApplyChanges(context.Background(), &plan.Changes{UpdateOld: updatesOld, UpdateNew: updatesNew})
   504  	if err != nil {
   505  		t.Fatal(err)
   506  	}
   507  
   508  	client.ForEachZone(func(zone *zones.Zone) error {
   509  		client.ForEachRecordSet(zone.ID, func(recordSet *recordsets.RecordSet) error {
   510  			for i, ex := range expected {
   511  				sort.Strings(recordSet.Records)
   512  				if reflect.DeepEqual(ex, recordSet) {
   513  					expected = append(expected[:i], expected[i+1:]...)
   514  					return nil
   515  				}
   516  			}
   517  			t.Errorf("unexpected record-set %s/%s -> %v", recordSet.Name, recordSet.Type, recordSet.Records)
   518  			return nil
   519  		})
   520  		return nil
   521  	})
   522  
   523  	if len(expected) != 0 {
   524  		t.Errorf("not all expected record-sets were updated. Remained: %v", expected)
   525  	}
   526  	return expectedCopy
   527  }
   528  
   529  func TestDesignateDeleteRecords(t *testing.T) {
   530  	client := newFakeDesignateClient()
   531  	testDesignateDeleteRecords(t, client)
   532  }
   533  
   534  func testDesignateDeleteRecords(t *testing.T, client *fakeDesignateClient) {
   535  	expected := testDesignateUpdateRecords(t, client)
   536  	deletes := []*endpoint.Endpoint{
   537  		{
   538  			DNSName:    "www.example.com.",
   539  			RecordType: endpoint.RecordTypeA,
   540  			Targets:    endpoint.Targets{"10.1.1.1"},
   541  			Labels: map[string]string{
   542  				designateZoneID:          "zone-1",
   543  				designateRecordSetID:     expected[0].ID,
   544  				designateOriginalRecords: "10.1.1.1",
   545  			},
   546  		},
   547  		{
   548  			DNSName:    "srv.test.net.",
   549  			RecordType: endpoint.RecordTypeA,
   550  			Targets:    endpoint.Targets{"10.2.1.1"},
   551  			Labels: map[string]string{
   552  				designateZoneID:          "zone-2",
   553  				designateRecordSetID:     expected[3].ID,
   554  				designateOriginalRecords: "10.2.1.1\00010.3.3.2",
   555  			},
   556  		},
   557  	}
   558  	expected[3].Records = []string{"10.3.3.2"}
   559  	expected = expected[1:]
   560  
   561  	err := client.ToProvider().ApplyChanges(context.Background(), &plan.Changes{Delete: deletes})
   562  	if err != nil {
   563  		t.Fatal(err)
   564  	}
   565  
   566  	client.ForEachZone(func(zone *zones.Zone) error {
   567  		client.ForEachRecordSet(zone.ID, func(recordSet *recordsets.RecordSet) error {
   568  			for i, ex := range expected {
   569  				sort.Strings(recordSet.Records)
   570  				if reflect.DeepEqual(ex, recordSet) {
   571  					expected = append(expected[:i], expected[i+1:]...)
   572  					return nil
   573  				}
   574  			}
   575  			t.Errorf("unexpected record-set %s/%s -> %v", recordSet.Name, recordSet.Type, recordSet.Records)
   576  			return nil
   577  		})
   578  		return nil
   579  	})
   580  
   581  	if len(expected) != 0 {
   582  		t.Errorf("not all expected record-sets were deleted. Remained: %v", expected)
   583  	}
   584  }