Skip to content

Commit

Permalink
Move function which generated composite URLMap to translator pkg
Browse files Browse the repository at this point in the history
  • Loading branch information
rramkumar1 committed May 12, 2020
1 parent 32f1e04 commit ccebf6e
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 150 deletions.
5 changes: 3 additions & 2 deletions pkg/loadbalancers/loadbalancers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
"k8s.io/ingress-gce/pkg/flags"
"k8s.io/ingress-gce/pkg/instances"
"k8s.io/ingress-gce/pkg/loadbalancers/features"
"k8s.io/ingress-gce/pkg/translator"
"k8s.io/ingress-gce/pkg/utils"
"k8s.io/ingress-gce/pkg/utils/common"
namer_util "k8s.io/ingress-gce/pkg/utils/namer"
Expand Down Expand Up @@ -1153,8 +1154,8 @@ func verifyURLMap(t *testing.T, j *testJig, feNamer namer_util.IngressFrontendNa
if err != nil || um == nil {
t.Errorf("j.fakeGCE.GetUrlMap(%q) = %v, %v; want _, nil", name, um, err)
}
wantComputeURLMap := toCompositeURLMap(wantGCEURLMap, feNamer, key)
if !mapsEqual(wantComputeURLMap, um) {
wantComputeURLMap := translator.ToCompositeURLMap(wantGCEURLMap, feNamer, key)
if !MapsEqual(wantComputeURLMap, um) {
t.Errorf("mapsEqual() = false, got\n%+v\n want\n%+v", um, wantComputeURLMap)
}
}
Expand Down
103 changes: 5 additions & 98 deletions pkg/loadbalancers/url_maps.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,12 @@ limitations under the License.
package loadbalancers

import (
"crypto/md5"
"encoding/hex"
"fmt"

"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud"
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/ingress-gce/pkg/composite"
"k8s.io/ingress-gce/pkg/translator"
"k8s.io/ingress-gce/pkg/utils"
"k8s.io/ingress-gce/pkg/utils/namer"
"k8s.io/klog"
)

Expand All @@ -49,7 +45,7 @@ func (l *L7) ensureComputeURLMap() error {
if err != nil {
return err
}
expectedMap := toCompositeURLMap(l.runtimeInfo.UrlMap, l.namer, key)
expectedMap := translator.ToCompositeURLMap(l.runtimeInfo.UrlMap, l.namer, key)
key.Name = expectedMap.Name

expectedMap.Version = l.Versions().UrlMap
Expand All @@ -67,7 +63,7 @@ func (l *L7) ensureComputeURLMap() error {
return nil
}

if mapsEqual(currentMap, expectedMap) {
if MapsEqual(currentMap, expectedMap) {
klog.V(4).Infof("URLMap for %q is unchanged", l)
l.um = currentMap
return nil
Expand Down Expand Up @@ -112,10 +108,10 @@ func getBackendNames(computeURLMap *composite.UrlMap) ([]string, error) {
return beNames.List(), nil
}

// mapsEqual compares the structure of two compute.UrlMaps.
// MapsEqual compares the structure of two compute.UrlMaps.
// The service strings are parsed and compared as resource paths (such as
// "global/backendServices/my-service") to ignore variables: endpoint, version, and project.
func mapsEqual(a, b *composite.UrlMap) bool {
func MapsEqual(a, b *composite.UrlMap) bool {
if !utils.EqualResourcePaths(a.DefaultService, b.DefaultService) {
return false
}
Expand Down Expand Up @@ -177,92 +173,3 @@ func mapsEqual(a, b *composite.UrlMap) bool {
return true
}

// toCompositeURLMap translates the given hostname: endpoint->port mapping into a gce url map.
//
// HostRule: Conceptually contains all PathRules for a given host.
// PathMatcher: Associates a path rule with a host rule. Mostly an optimization.
// PathRule: Maps a single path regex to a backend.
//
// The GCE url map allows multiple hosts to share url->backend mappings without duplication, eg:
// Host: foo(PathMatcher1), bar(PathMatcher1,2)
// PathMatcher1:
// /a -> b1
// /b -> b2
// PathMatcher2:
// /c -> b1
// This leads to a lot of complexity in the common case, where all we want is a mapping of
// host->{/path: backend}.
//
// Consider some alternatives:
// 1. Using a single backend per PathMatcher:
// Host: foo(PathMatcher1,3) bar(PathMatcher1,2,3)
// PathMatcher1:
// /a -> b1
// PathMatcher2:
// /c -> b1
// PathMatcher3:
// /b -> b2
// 2. Using a single host per PathMatcher:
// Host: foo(PathMatcher1)
// PathMatcher1:
// /a -> b1
// /b -> b2
// Host: bar(PathMatcher2)
// PathMatcher2:
// /a -> b1
// /b -> b2
// /c -> b1
// In the context of kubernetes services, 2 makes more sense, because we
// rarely want to lookup backends (service:nodeport). When a service is
// deleted, we need to find all host PathMatchers that have the backend
// and remove the mapping. When a new path is added to a host (happens
// more frequently than service deletion) we just need to lookup the 1
// pathmatcher of the host.
func toCompositeURLMap(g *utils.GCEURLMap, namer namer.IngressFrontendNamer, key *meta.Key) *composite.UrlMap {
defaultBackendName := g.DefaultBackend.BackendName()
key.Name = defaultBackendName
resourceID := cloud.ResourceID{ProjectID: "", Resource: "backendServices", Key: key}
m := &composite.UrlMap{
Name: namer.UrlMap(),
DefaultService: resourceID.ResourcePath(),
}

for _, hostRule := range g.HostRules {
// Create a host rule
// Create a path matcher
// Add all given endpoint:backends to pathRules in path matcher
pmName := getNameForPathMatcher(hostRule.Hostname)
m.HostRules = append(m.HostRules, &composite.HostRule{
Hosts: []string{hostRule.Hostname},
PathMatcher: pmName,
})

pathMatcher := &composite.PathMatcher{
Name: pmName,
DefaultService: m.DefaultService,
PathRules: []*composite.PathRule{},
}

// GCE ensures that matched rule with longest prefix wins.
for _, rule := range hostRule.Paths {
beName := rule.Backend.BackendName()
key.Name = beName
resourceID := cloud.ResourceID{ProjectID: "", Resource: "backendServices", Key: key}
beLink := resourceID.ResourcePath()
pathMatcher.PathRules = append(pathMatcher.PathRules, &composite.PathRule{
Paths: []string{rule.Path},
Service: beLink,
})
}
m.PathMatchers = append(m.PathMatchers, pathMatcher)
}
return m
}

// getNameForPathMatcher returns a name for a pathMatcher based on the given host rule.
// The host rule can be a regex, the path matcher name used to associate the 2 cannot.
func getNameForPathMatcher(hostRule string) string {
hasher := md5.New()
hasher.Write([]byte(hostRule))
return fmt.Sprintf("%v%v", hostRulePrefix, hex.EncodeToString(hasher.Sum(nil)))
}
52 changes: 2 additions & 50 deletions pkg/loadbalancers/url_maps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,8 @@ package loadbalancers
import (
"testing"

"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/ingress-gce/pkg/composite"
"k8s.io/ingress-gce/pkg/utils"
namer_util "k8s.io/ingress-gce/pkg/utils/namer"
)

func TestComputeURLMapEquals(t *testing.T) {
Expand All @@ -32,63 +29,18 @@ func TestComputeURLMapEquals(t *testing.T) {
m := testCompositeURLMap()
// Test equality.
same := testCompositeURLMap()
if !mapsEqual(m, same) {
if !MapsEqual(m, same) {
t.Errorf("mapsEqual(%+v, %+v) = true, want false", m, same)
}

// Test different default backend.
diffDefault := testCompositeURLMap()
diffDefault.DefaultService = "/global/backendServices/some-service"
if mapsEqual(m, diffDefault) {
if MapsEqual(m, diffDefault) {
t.Errorf("mapsEqual(%+v, %+v) = true, want false", m, diffDefault)
}
}

func TestToComputeURLMap(t *testing.T) {
t.Parallel()

wantComputeMap := testCompositeURLMap()
namer := namer_util.NewNamer("uid1", "fw1")
gceURLMap := &utils.GCEURLMap{
DefaultBackend: &utils.ServicePort{NodePort: 30000, BackendNamer: namer},
HostRules: []utils.HostRule{
{
Hostname: "abc.com",
Paths: []utils.PathRule{
{
Path: "/web",
Backend: utils.ServicePort{NodePort: 32000, BackendNamer: namer},
},
{
Path: "/other",
Backend: utils.ServicePort{NodePort: 32500, BackendNamer: namer},
},
},
},
{
Hostname: "foo.bar.com",
Paths: []utils.PathRule{
{
Path: "/",
Backend: utils.ServicePort{NodePort: 33000, BackendNamer: namer},
},
{
Path: "/*",
Backend: utils.ServicePort{NodePort: 33500, BackendNamer: namer},
},
},
},
},
}

namerFactory := namer_util.NewFrontendNamerFactory(namer, "")
feNamer := namerFactory.NamerForLoadBalancer("ns/lb-name")
gotComputeURLMap := toCompositeURLMap(gceURLMap, feNamer, meta.GlobalKey("ns-lb-name"))
if !mapsEqual(gotComputeURLMap, wantComputeMap) {
t.Errorf("toComputeURLMap() = \n%+v\n want\n%+v", gotComputeURLMap, wantComputeMap)
}
}

func testCompositeURLMap() *composite.UrlMap {
return &composite.UrlMap{
Name: "k8s-um-lb-name",
Expand Down
123 changes: 123 additions & 0 deletions pkg/translator/translator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
Copyright 2020 The Kubernetes Authors.
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.
*/

package translator

import (
"crypto/md5"
"encoding/hex"
"fmt"

"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud"
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"
"k8s.io/ingress-gce/pkg/composite"
"k8s.io/ingress-gce/pkg/utils"
"k8s.io/ingress-gce/pkg/utils/namer"
)


// The gce api uses the name of a path rule to match a host rule.
const hostRulePrefix = "host"

// ToCompositeURLMap translates the given hostname: endpoint->port mapping into a gce url map.
//
// HostRule: Conceptually contains all PathRules for a given host.
// PathMatcher: Associates a path rule with a host rule. Mostly an optimization.
// PathRule: Maps a single path regex to a backend.
//
// The GCE url map allows multiple hosts to share url->backend mappings without duplication, eg:
// Host: foo(PathMatcher1), bar(PathMatcher1,2)
// PathMatcher1:
// /a -> b1
// /b -> b2
// PathMatcher2:
// /c -> b1
// This leads to a lot of complexity in the common case, where all we want is a mapping of
// host->{/path: backend}.
//
// Consider some alternatives:
// 1. Using a single backend per PathMatcher:
// Host: foo(PathMatcher1,3) bar(PathMatcher1,2,3)
// PathMatcher1:
// /a -> b1
// PathMatcher2:
// /c -> b1
// PathMatcher3:
// /b -> b2
// 2. Using a single host per PathMatcher:
// Host: foo(PathMatcher1)
// PathMatcher1:
// /a -> b1
// /b -> b2
// Host: bar(PathMatcher2)
// PathMatcher2:
// /a -> b1
// /b -> b2
// /c -> b1
// In the context of kubernetes services, 2 makes more sense, because we
// rarely want to lookup backends (service:nodeport). When a service is
// deleted, we need to find all host PathMatchers that have the backend
// and remove the mapping. When a new path is added to a host (happens
// more frequently than service deletion) we just need to lookup the 1
// pathmatcher of the host.
func ToCompositeURLMap(g *utils.GCEURLMap, namer namer.IngressFrontendNamer, key *meta.Key) *composite.UrlMap {
defaultBackendName := g.DefaultBackend.BackendName()
key.Name = defaultBackendName
resourceID := cloud.ResourceID{ProjectID: "", Resource: "backendServices", Key: key}
m := &composite.UrlMap{
Name: namer.UrlMap(),
DefaultService: resourceID.ResourcePath(),
}

for _, hostRule := range g.HostRules {
// Create a host rule
// Create a path matcher
// Add all given endpoint:backends to pathRules in path matcher
pmName := getNameForPathMatcher(hostRule.Hostname)
m.HostRules = append(m.HostRules, &composite.HostRule{
Hosts: []string{hostRule.Hostname},
PathMatcher: pmName,
})

pathMatcher := &composite.PathMatcher{
Name: pmName,
DefaultService: m.DefaultService,
PathRules: []*composite.PathRule{},
}

// GCE ensures that matched rule with longest prefix wins.
for _, rule := range hostRule.Paths {
beName := rule.Backend.BackendName()
key.Name = beName
resourceID := cloud.ResourceID{ProjectID: "", Resource: "backendServices", Key: key}
beLink := resourceID.ResourcePath()
pathMatcher.PathRules = append(pathMatcher.PathRules, &composite.PathRule{
Paths: []string{rule.Path},
Service: beLink,
})
}
m.PathMatchers = append(m.PathMatchers, pathMatcher)
}
return m
}

// getNameForPathMatcher returns a name for a pathMatcher based on the given host rule.
// The host rule can be a regex, the path matcher name used to associate the 2 cannot.
func getNameForPathMatcher(hostRule string) string {
hasher := md5.New()
hasher.Write([]byte(hostRule))
return fmt.Sprintf("%v%v", hostRulePrefix, hex.EncodeToString(hasher.Sum(nil)))
}

Loading

0 comments on commit ccebf6e

Please sign in to comment.