Skip to content

Commit

Permalink
Weight Round Robin (3/4) - Endpoint
Browse files Browse the repository at this point in the history
related to #50

implemented code that adds  labels to the local dnsEndpoint.

 - covered by unit-tests
 - the labels are as follows:
```yaml
kind: GSLB
spec:
  ingress:
    rules:
      - host: roundrobin.cloud.example.com
        http: # This section mirrors the same structure as that of an Ingress resource and will be used verbatim when creating the corresponding Ingress resource that will match the GSLB host
          paths:
            - path: /
              backend:
                service:
                  name: existing-app # Gslb should reflect NotFound status
                  port:
                    name: http
spec:
  strategy: roundRobin
    weight:
      eu: 35%
      us: 50%
      za: 15%
```

has local DNS endpoint like this:

```yaml
apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
  name: app_host
  annotations: k8gb.absa.oss/dnstype: local
spec:
  endpoints:
  - dnsName: app.bar.com
    labels:
      weight-eu-0-35: 10.10.0.1
      weight-eu-1-35: 10.10.0.2
      weight-us-0-50: 10.0.0.1
      weight-us-1-50: 10.0.0.2
      weight-za-0-15: 10.22.0.1
      weight-za-1-15: 10.22.0.2
      weight-za-2-15: 10.22.1.1
    recordTTL: 180
    recordType: A
    Targets:
      - 10.10.0.1
      - 10.10.0.2
      - 10.0.0.1
      - 10.0.0.2
      - 10.22.0.1
      - 10.22.0.2
      - 10.22.1.1
```
Signed-off-by: kuritka <[email protected]>
  • Loading branch information
kuritka committed Jun 30, 2022
1 parent da5ec2c commit ca196cf
Show file tree
Hide file tree
Showing 6 changed files with 326 additions and 11 deletions.
26 changes: 25 additions & 1 deletion controllers/dnsupdate.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"strings"

"github.com/k8gb-io/k8gb/controllers/depresolver"
"github.com/k8gb-io/k8gb/controllers/providers/assistant"

k8gbv1beta1 "github.com/k8gb-io/k8gb/api/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -54,6 +55,7 @@ func (r *GslbReconciler) gslbDNSEndpoint(gslb *k8gbv1beta1.Gslb) (*externaldns.D

for host, health := range serviceHealth {
var finalTargets []string
var finals = assistant.NewTargets()

if !strings.Contains(host, r.Config.EdgeDNSZone) {
return nil, fmt.Errorf("ingress host %s does not match delegated zone %s", host, r.Config.EdgeDNSZone)
Expand All @@ -65,6 +67,7 @@ func (r *GslbReconciler) gslbDNSEndpoint(gslb *k8gbv1beta1.Gslb) (*externaldns.D
if isHealthy {
finalTargets = append(finalTargets, localTargets...)
localTargetsHost := fmt.Sprintf("localtargets-%s", host)
finals.Append(r.Config.ClusterGeoTag, localTargets)
dnsRecord := &externaldns.Endpoint{
DNSName: localTargetsHost,
RecordTTL: ttl,
Expand All @@ -75,7 +78,9 @@ func (r *GslbReconciler) gslbDNSEndpoint(gslb *k8gbv1beta1.Gslb) (*externaldns.D
}

// Check if host is alive on external Gslb
externalTargets := r.DNSProvider.GetExternalTargets(host).GetIPs()
externals := r.DNSProvider.GetExternalTargets(host)
finals.AppendTargets(externals)
externalTargets := externals.GetIPs()

sortTargets(externalTargets)

Expand Down Expand Up @@ -132,6 +137,9 @@ func (r *GslbReconciler) gslbDNSEndpoint(gslb *k8gbv1beta1.Gslb) (*externaldns.D
"strategy": gslb.Spec.Strategy.Type,
},
}
for k, v := range r.getLabels(gslb, finals) {
dnsRecord.Labels[k] = v
}
gslbHosts = append(gslbHosts, dnsRecord)
}
}
Expand Down Expand Up @@ -166,3 +174,19 @@ func (r *GslbReconciler) updateRuntimeStatus(gslb *k8gbv1beta1.Gslb, isPrimary b
m.UpdateFailoverStatus(gslb, isPrimary, isHealthy, finalTargets)
}
}

// getLabels map of where key identifies region and weight, value identifies IP.
func (r *GslbReconciler) getLabels(gslb *k8gbv1beta1.Gslb, targets assistant.Targets) (labels map[string]string) {
labels = make(map[string]string, 0)
for k, v := range gslb.Spec.Strategy.Weight {
t, found := targets[k]
if !found {
continue
}
for i, ip := range t.IPs {
l := fmt.Sprintf("weight-%s-%v-%v", k, i, v.Int())
labels[l] = ip
}
}
return labels
}
6 changes: 3 additions & 3 deletions controllers/providers/assistant/gslb.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,8 +297,8 @@ func dnsQuery(host string, nameservers utils.DNSList) (*dns.Msg, error) {
}

func (r *Gslb) GetExternalTargets(host string, extClusterNsNames map[string]string) (targets Targets) {
targets = Targets{}
for _, cluster := range extClusterNsNames {
targets = NewTargets()
for tag, cluster := range extClusterNsNames {
// Use edgeDNSServer for resolution of NS names and fallback to local nameservers
log.Info().
Str("cluster", cluster).
Expand Down Expand Up @@ -327,7 +327,7 @@ func (r *Gslb) GetExternalTargets(host string, extClusterNsNames map[string]stri
}
clusterTargets := getARecords(a)
if len(clusterTargets) > 0 {
targets = append(targets, Target{cluster, clusterTargets})
targets[tag] = &Target{clusterTargets}
log.Info().
Strs("clusterTargets", clusterTargets).
Str("cluster", cluster).
Expand Down
31 changes: 24 additions & 7 deletions controllers/providers/assistant/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,34 @@ Generated by GoLic, for more details see: https://github.com/AbsaOSS/golic
*/

type Target struct {
Region string
IPs []string
IPs []string
}

type Targets []Target
type Targets map[string]*Target

func (t Targets) GetIPs() (targets []string) {
func NewTargets() Targets {
return make(map[string]*Target, 0)
}

func (t Targets) GetIPs() (ips []string) {
// initializing targets to avoid possible nil reference errors (serialization etc.)
targets = []string{}
ips = []string{}
for _, v := range t {
targets = append(targets, v.IPs...)
ips = append(ips, v.IPs...)
}
return ips
}

func (t Targets) Append(tag string, ips []string) {
if target, found := t[tag]; found {
target.IPs = append(target.IPs, ips...)
return
}
t[tag] = &Target{IPs: ips}
}

func (t Targets) AppendTargets(targets Targets) {
for k, v := range targets {
t.Append(k, v.IPs)
}
return targets
}
198 changes: 198 additions & 0 deletions controllers/weight_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package controllers

import (
"context"
"fmt"
"testing"

"github.com/golang/mock/gomock"
k8gbv1beta1 "github.com/k8gb-io/k8gb/api/v1beta1"
"github.com/k8gb-io/k8gb/controllers/depresolver"
"github.com/k8gb-io/k8gb/controllers/providers/assistant"
"github.com/k8gb-io/k8gb/controllers/providers/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/controller-runtime/pkg/client"
externaldns "sigs.k8s.io/external-dns/endpoint"
)

/*
Copyright 2022 The k8gb Contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Generated by GoLic, for more details see: https://github.com/AbsaOSS/golic
*/

func TestWeight(t *testing.T) {
// arrange
type wrr struct {
weight string
targets []string
}
var tests = []struct {
name string
data map[string]wrr
injectWeights bool
expectedLabels map[string]string
}{
{
name: "eu35-us50-za15",
injectWeights: true,
data: map[string]wrr{
"eu": {weight: "35%", targets: []string{"10.10.0.1", "10.10.0.2"}},
"us": {weight: "50%", targets: []string{"10.0.0.1", "10.0.0.2"}},
"za": {weight: "15%", targets: []string{"10.22.0.1", "10.22.0.2", "10.22.1.1"}},
},
expectedLabels: map[string]string{
"strategy": depresolver.RoundRobinStrategy,
"weight-us-0-50": "10.0.0.1",
"weight-us-1-50": "10.0.0.2",
"weight-eu-0-35": "10.10.0.1",
"weight-eu-1-35": "10.10.0.2",
"weight-za-0-15": "10.22.0.1",
"weight-za-1-15": "10.22.0.2",
"weight-za-2-15": "10.22.1.1",
},
},
{
name: "eu100-us0-za0",
injectWeights: true,
data: map[string]wrr{
"eu": {weight: "100%", targets: []string{"10.10.0.1", "10.10.0.2"}},
"us": {weight: "0%", targets: []string{"10.0.0.1", "10.0.0.2"}},
"za": {weight: "0%", targets: []string{"10.22.0.1", "10.22.0.2", "10.22.1.1"}},
},
expectedLabels: map[string]string{
"strategy": depresolver.RoundRobinStrategy,
"weight-us-0-0": "10.0.0.1",
"weight-us-1-0": "10.0.0.2",
"weight-eu-0-100": "10.10.0.1",
"weight-eu-1-100": "10.10.0.2",
"weight-za-0-0": "10.22.0.1",
"weight-za-1-0": "10.22.0.2",
"weight-za-2-0": "10.22.1.1",
},
},
{
name: "weights-without-external-targets",
injectWeights: true,
data: map[string]wrr{
"eu": {weight: "25%", targets: []string{}},
"us": {weight: "75%", targets: []string{}},
"za": {weight: "0%", targets: []string{}},
},
expectedLabels: map[string]string{
"strategy": depresolver.RoundRobinStrategy,
},
},
{
name: "weights-without-external-targets",
injectWeights: true,
data: map[string]wrr{
"eu": {weight: "25%", targets: []string{}},
"us": {weight: "75%", targets: []string{}},
"za": {weight: "0%", targets: []string{}},
},
expectedLabels: map[string]string{
"strategy": depresolver.RoundRobinStrategy,
"weight-us-0-0": "",
"weight-eu-0-0": "",
"weight-za-0-0": "",
},
},
{
name: "no weights without external targets",
injectWeights: false,
data: map[string]wrr{},
expectedLabels: map[string]string{
"strategy": depresolver.RoundRobinStrategy,
},
},
{
name: "no weights with external targets",
injectWeights: false,
data: map[string]wrr{
"eu": {weight: "100%", targets: []string{"10.10.0.1", "10.10.0.2"}},
"us": {weight: "0%", targets: []string{"10.0.0.1", "10.0.0.2"}},
"za": {weight: "0%", targets: []string{"10.22.0.1", "10.22.0.2", "10.22.1.1"}},
},
expectedLabels: map[string]string{
"strategy": depresolver.RoundRobinStrategy,
},
},
{
name: "empty weights",
injectWeights: true,
data: map[string]wrr{},
expectedLabels: map[string]string{
"strategy": depresolver.RoundRobinStrategy,
},
},
}

// act
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {

injectWeight := func(ctx context.Context, gslb *k8gbv1beta1.Gslb, client client.Client) error {
if !test.injectWeights {
return nil
}
gslb.Spec.Strategy.Weight = make(map[string]k8gbv1beta1.Percentage, 0)
for k, w := range test.data {
gslb.Spec.Strategy.Weight[k] = k8gbv1beta1.Percentage(w.weight)
}
return nil
}

assertAnnotation := func(gslb *k8gbv1beta1.Gslb, ep *externaldns.DNSEndpoint) error {
require.NotNil(t, ep)
require.NotNil(t, gslb)
// annotation is equal to tested value
for _, e := range ep.Spec.Endpoints {
for k, v := range e.Labels {
assert.Equal(t, test.expectedLabels[k], v)
}
assert.Equal(t, len(test.expectedLabels), len(e.Labels))
}
return nil
}

ctrl := gomock.NewController(t)
defer ctrl.Finish()
// settings := provideSettings(t, predefinedConfig)
m := dns.NewMockProvider(ctrl)
r := depresolver.NewMockResolver(ctrl)
m.EXPECT().GslbIngressExposedIPs(gomock.Any()).Return([]string{}, nil).Times(1)
m.EXPECT().SaveDNSEndpoint(gomock.Any(), gomock.Any()).Do(assertAnnotation).Return(fmt.Errorf("save DNS error")).Times(1)
m.EXPECT().CreateZoneDelegationForExternalDNS(gomock.Any()).Return(nil).AnyTimes()
r.EXPECT().ResolveGslbSpec(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(injectWeight).AnyTimes()

ts := assistant.Targets{}
for k, w := range test.data {
ts[k] = &assistant.Target{IPs: w.targets}
}
m.EXPECT().GetExternalTargets("roundrobin.cloud.example.com").Return(ts).Times(1)
m.EXPECT().GetExternalTargets("notfound.cloud.example.com").Return(assistant.Targets{}).Times(1)
m.EXPECT().GetExternalTargets("unhealthy.cloud.example.com").Return(assistant.Targets{}).Times(1)

settings := provideSettings(t, predefinedConfig)
settings.reconciler.DNSProvider = m
settings.reconciler.DepResolver = r

// act, assert
_, _ = settings.reconciler.Reconcile(context.TODO(), settings.request)
})
}
}
Empty file.
Loading

0 comments on commit ca196cf

Please sign in to comment.