sigs.k8s.io/external-dns@v0.14.1/source/ambassador_host_test.go (about) 1 /* 2 Copyright 2019 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 source 18 19 import ( 20 "fmt" 21 "testing" 22 23 ambassador "github.com/datawire/ambassador/pkg/api/getambassador.io/v2" 24 "github.com/stretchr/testify/assert" 25 "github.com/stretchr/testify/require" 26 "github.com/stretchr/testify/suite" 27 "golang.org/x/net/context" 28 v1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 31 "k8s.io/apimachinery/pkg/runtime" 32 fakeDynamic "k8s.io/client-go/dynamic/fake" 33 fakeKube "k8s.io/client-go/kubernetes/fake" 34 "sigs.k8s.io/external-dns/endpoint" 35 ) 36 37 const defaultAmbassadorNamespace = "ambassador" 38 const defaultAmbassadorServiceName = "ambassador" 39 40 type AmbassadorSuite struct { 41 suite.Suite 42 } 43 44 func TestAmbassadorSource(t *testing.T) { 45 suite.Run(t, new(AmbassadorSuite)) 46 t.Run("Interface", testAmbassadorSourceImplementsSource) 47 } 48 49 // testAmbassadorSourceImplementsSource tests that ambassadorHostSource is a valid Source. 50 func testAmbassadorSourceImplementsSource(t *testing.T) { 51 require.Implements(t, (*Source)(nil), new(ambassadorHostSource)) 52 } 53 54 func TestAmbassadorHostSource(t *testing.T) { 55 t.Parallel() 56 57 hostAnnotation := fmt.Sprintf("%s/%s", defaultAmbassadorNamespace, defaultAmbassadorServiceName) 58 59 for _, ti := range []struct { 60 title string 61 host ambassador.Host 62 service v1.Service 63 expected []*endpoint.Endpoint 64 }{ 65 { 66 title: "Simple host", 67 host: ambassador.Host{ 68 ObjectMeta: metav1.ObjectMeta{ 69 Name: "basic-host", 70 Annotations: map[string]string{ 71 ambHostAnnotation: hostAnnotation, 72 }, 73 }, 74 Spec: &ambassador.HostSpec{ 75 Hostname: "www.example.org", 76 }, 77 }, 78 service: v1.Service{ 79 ObjectMeta: metav1.ObjectMeta{ 80 Name: defaultAmbassadorServiceName, 81 }, 82 Status: v1.ServiceStatus{ 83 LoadBalancer: v1.LoadBalancerStatus{ 84 Ingress: []v1.LoadBalancerIngress{{ 85 IP: "1.1.1.1", 86 }}, 87 }, 88 }, 89 }, 90 expected: []*endpoint.Endpoint{ 91 { 92 DNSName: "www.example.org", 93 RecordType: endpoint.RecordTypeA, 94 Targets: endpoint.Targets{"1.1.1.1"}, 95 }, 96 }, 97 }, { 98 title: "Service with load balancer hostname", 99 host: ambassador.Host{ 100 ObjectMeta: metav1.ObjectMeta{ 101 Name: "basic-host", 102 Annotations: map[string]string{ 103 ambHostAnnotation: hostAnnotation, 104 }, 105 }, 106 Spec: &ambassador.HostSpec{ 107 Hostname: "www.example.org", 108 }, 109 }, 110 service: v1.Service{ 111 ObjectMeta: metav1.ObjectMeta{ 112 Name: defaultAmbassadorServiceName, 113 }, 114 Status: v1.ServiceStatus{ 115 LoadBalancer: v1.LoadBalancerStatus{ 116 Ingress: []v1.LoadBalancerIngress{{ 117 Hostname: "dns.google", 118 }}, 119 }, 120 }, 121 }, 122 expected: []*endpoint.Endpoint{ 123 { 124 DNSName: "www.example.org", 125 RecordType: endpoint.RecordTypeCNAME, 126 Targets: endpoint.Targets{"dns.google"}, 127 }, 128 }, 129 }, { 130 title: "Service with external IP", 131 host: ambassador.Host{ 132 ObjectMeta: metav1.ObjectMeta{ 133 Name: "service-external-ip", 134 Annotations: map[string]string{ 135 ambHostAnnotation: hostAnnotation, 136 }, 137 }, 138 Spec: &ambassador.HostSpec{ 139 Hostname: "www.example.org", 140 }, 141 }, 142 service: v1.Service{ 143 ObjectMeta: metav1.ObjectMeta{ 144 Name: defaultAmbassadorServiceName, 145 }, 146 Spec: v1.ServiceSpec{ 147 ExternalIPs: []string{"2.2.2.2"}, 148 }, 149 Status: v1.ServiceStatus{ 150 LoadBalancer: v1.LoadBalancerStatus{ 151 Ingress: []v1.LoadBalancerIngress{{ 152 IP: "1.1.1.1", 153 }}, 154 }, 155 }, 156 }, 157 expected: []*endpoint.Endpoint{ 158 { 159 DNSName: "www.example.org", 160 RecordType: endpoint.RecordTypeA, 161 Targets: endpoint.Targets{"2.2.2.2"}, 162 }, 163 }, 164 }, { 165 title: "Host with target annotation", 166 host: ambassador.Host{ 167 ObjectMeta: metav1.ObjectMeta{ 168 Name: "basic-host", 169 Annotations: map[string]string{ 170 ambHostAnnotation: hostAnnotation, 171 targetAnnotationKey: "3.3.3.3", 172 }, 173 }, 174 Spec: &ambassador.HostSpec{ 175 Hostname: "www.example.org", 176 }, 177 }, 178 service: v1.Service{ 179 ObjectMeta: metav1.ObjectMeta{ 180 Name: defaultAmbassadorServiceName, 181 }, 182 Status: v1.ServiceStatus{ 183 LoadBalancer: v1.LoadBalancerStatus{ 184 Ingress: []v1.LoadBalancerIngress{{ 185 IP: "1.1.1.1", 186 }}, 187 }, 188 }, 189 }, 190 expected: []*endpoint.Endpoint{ 191 { 192 DNSName: "www.example.org", 193 RecordType: endpoint.RecordTypeA, 194 Targets: endpoint.Targets{"3.3.3.3"}, 195 }, 196 }, 197 }, { 198 title: "Host with TTL annotation", 199 host: ambassador.Host{ 200 ObjectMeta: metav1.ObjectMeta{ 201 Name: "basic-host", 202 Annotations: map[string]string{ 203 ambHostAnnotation: hostAnnotation, 204 ttlAnnotationKey: "180", 205 }, 206 }, 207 Spec: &ambassador.HostSpec{ 208 Hostname: "www.example.org", 209 }, 210 }, 211 service: v1.Service{ 212 ObjectMeta: metav1.ObjectMeta{ 213 Name: defaultAmbassadorServiceName, 214 }, 215 Status: v1.ServiceStatus{ 216 LoadBalancer: v1.LoadBalancerStatus{ 217 Ingress: []v1.LoadBalancerIngress{{ 218 IP: "1.1.1.1", 219 }}, 220 }, 221 }, 222 }, 223 expected: []*endpoint.Endpoint{ 224 { 225 DNSName: "www.example.org", 226 RecordType: endpoint.RecordTypeA, 227 Targets: endpoint.Targets{"1.1.1.1"}, 228 RecordTTL: 180, 229 }, 230 }, 231 }, { 232 title: "Host with provider specific annotation", 233 host: ambassador.Host{ 234 ObjectMeta: metav1.ObjectMeta{ 235 Name: "basic-host", 236 Annotations: map[string]string{ 237 ambHostAnnotation: hostAnnotation, 238 CloudflareProxiedKey: "true", 239 }, 240 }, 241 Spec: &ambassador.HostSpec{ 242 Hostname: "www.example.org", 243 }, 244 }, 245 service: v1.Service{ 246 ObjectMeta: metav1.ObjectMeta{ 247 Name: defaultAmbassadorServiceName, 248 }, 249 Status: v1.ServiceStatus{ 250 LoadBalancer: v1.LoadBalancerStatus{ 251 Ingress: []v1.LoadBalancerIngress{{ 252 IP: "1.1.1.1", 253 }}, 254 }, 255 }, 256 }, 257 expected: []*endpoint.Endpoint{ 258 { 259 DNSName: "www.example.org", 260 RecordType: endpoint.RecordTypeA, 261 Targets: endpoint.Targets{"1.1.1.1"}, 262 ProviderSpecific: endpoint.ProviderSpecific{{ 263 Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied", 264 Value: "true", 265 }}, 266 }, 267 }, 268 }, { 269 title: "Host with missing Ambassador annotation", 270 host: ambassador.Host{ 271 ObjectMeta: metav1.ObjectMeta{ 272 Name: "basic-host", 273 }, 274 Spec: &ambassador.HostSpec{ 275 Hostname: "www.example.org", 276 }, 277 }, 278 service: v1.Service{ 279 ObjectMeta: metav1.ObjectMeta{ 280 Name: defaultAmbassadorServiceName, 281 }, 282 Status: v1.ServiceStatus{ 283 LoadBalancer: v1.LoadBalancerStatus{ 284 Ingress: []v1.LoadBalancerIngress{{ 285 IP: "1.1.1.1", 286 }}, 287 }, 288 }, 289 }, 290 expected: []*endpoint.Endpoint{}, 291 }, 292 } { 293 ti := ti 294 t.Run(ti.title, func(t *testing.T) { 295 t.Parallel() 296 297 fakeKubernetesClient := fakeKube.NewSimpleClientset() 298 ambassadorScheme := runtime.NewScheme() 299 ambassador.AddToScheme(ambassadorScheme) 300 fakeDynamicClient := fakeDynamic.NewSimpleDynamicClient(ambassadorScheme) 301 302 namespace := "default" 303 304 // Create Ambassador service 305 _, err := fakeKubernetesClient.CoreV1().Services(defaultAmbassadorNamespace).Create(context.Background(), &ti.service, metav1.CreateOptions{}) 306 assert.NoError(t, err) 307 308 // Create host resource 309 host, err := createAmbassadorHost(&ti.host) 310 assert.NoError(t, err) 311 312 _, err = fakeDynamicClient.Resource(ambHostGVR).Namespace(namespace).Create(context.Background(), host, metav1.CreateOptions{}) 313 assert.NoError(t, err) 314 315 source, err := NewAmbassadorHostSource(context.TODO(), fakeDynamicClient, fakeKubernetesClient, namespace) 316 assert.NoError(t, err) 317 assert.NotNil(t, source) 318 319 endpoints, err := source.Endpoints(context.Background()) 320 assert.NoError(t, err) 321 // Validate returned endpoints against expected endpoints. 322 validateEndpoints(t, endpoints, ti.expected) 323 }) 324 } 325 } 326 327 func createAmbassadorHost(host *ambassador.Host) (*unstructured.Unstructured, error) { 328 obj := &unstructured.Unstructured{} 329 uc, _ := newUnstructuredConverter() 330 err := uc.scheme.Convert(host, obj, nil) 331 332 return obj, err 333 } 334 335 // TestParseAmbLoadBalancerService tests our parsing of Ambassador service info. 336 func TestParseAmbLoadBalancerService(t *testing.T) { 337 vectors := []struct { 338 input string 339 ns string 340 svc string 341 errstr string 342 }{ 343 {"svc", "default", "svc", ""}, 344 {"ns/svc", "ns", "svc", ""}, 345 {"svc.ns", "ns", "svc", ""}, 346 {"svc.ns.foo.bar", "ns.foo.bar", "svc", ""}, 347 {"ns/svc/foo/bar", "", "", "invalid external-dns service: ns/svc/foo/bar"}, 348 {"ns/svc/foo.bar", "", "", "invalid external-dns service: ns/svc/foo.bar"}, 349 {"ns.foo/svc/bar", "", "", "invalid external-dns service: ns.foo/svc/bar"}, 350 } 351 352 for _, v := range vectors { 353 ns, svc, err := parseAmbLoadBalancerService(v.input) 354 355 errstr := "" 356 357 if err != nil { 358 errstr = err.Error() 359 } 360 if v.ns != ns { 361 t.Errorf("%s: got ns \"%s\", wanted \"%s\"", v.input, ns, v.ns) 362 } 363 364 if v.svc != svc { 365 t.Errorf("%s: got svc \"%s\", wanted \"%s\"", v.input, svc, v.svc) 366 } 367 368 if v.errstr != errstr { 369 t.Errorf("%s: got err \"%s\", wanted \"%s\"", v.input, errstr, v.errstr) 370 } 371 } 372 }