forked from kubernetes-sigs/external-dns
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Multi target plan ( Page Not Found ) (kubernetes-sigs#404)
* Make suitableType() be Endpoint method With this change it becomes possible to work with endpoint of empty type in packages other than "provider". Also it seems logical for a smart property getter without side effects to be a method rather than a function in different package * Make plan computation work correctly with multi-target domains * fix drawing * drop comments * fix boilerplate header * fix comment * fix the bug with empty map * rework registry to support random lables * serialize->serializeLabel function rename * golint for err variable naming * add additional test * add tests for current case where one resource can generate multiple endpoints * make labels have its own type, add serialization as a method * add comment for exported error * use greater rather than not equal zero * update changelog
- Loading branch information
Showing
17 changed files
with
980 additions
and
261 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
/* | ||
Copyright 2017 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 endpoint | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"sort" | ||
"strings" | ||
) | ||
|
||
var ( | ||
// ErrInvalidHeritage is returned when heritage was not found, or different heritage is found | ||
ErrInvalidHeritage = errors.New("heritage is unknown or not found") | ||
) | ||
|
||
const ( | ||
heritage = "external-dns" | ||
// OwnerLabelKey is the name of the label that defines the owner of an Endpoint. | ||
OwnerLabelKey = "owner" | ||
// ResourceLabelKey is the name of the label that identifies k8s resource which wants to acquire the DNS name | ||
ResourceLabelKey = "resource" | ||
) | ||
|
||
// Labels store metadata related to the endpoint | ||
// it is then stored in a persistent storage via serialization | ||
type Labels map[string]string | ||
|
||
// NewLabels returns empty Labels | ||
func NewLabels() Labels { | ||
return map[string]string{} | ||
} | ||
|
||
// NewLabelsFromString constructs endpoints labels from a provided format string | ||
// if heritage set to another value is found then error is returned | ||
// no heritage automatically assumes is not owned by external-dns and returns invalidHeritage error | ||
func NewLabelsFromString(labelText string) (Labels, error) { | ||
endpointLabels := map[string]string{} | ||
labelText = strings.Trim(labelText, "\"") // drop quotes | ||
tokens := strings.Split(labelText, ",") | ||
foundExternalDNSHeritage := false | ||
for _, token := range tokens { | ||
if len(strings.Split(token, "=")) != 2 { | ||
continue | ||
} | ||
key := strings.Split(token, "=")[0] | ||
val := strings.Split(token, "=")[1] | ||
if key == "heritage" && val != heritage { | ||
return nil, ErrInvalidHeritage | ||
} | ||
if key == "heritage" { | ||
foundExternalDNSHeritage = true | ||
continue | ||
} | ||
if strings.HasPrefix(key, heritage) { | ||
endpointLabels[strings.TrimPrefix(key, heritage+"/")] = val | ||
} | ||
} | ||
|
||
if !foundExternalDNSHeritage { | ||
return nil, ErrInvalidHeritage | ||
} | ||
|
||
return endpointLabels, nil | ||
} | ||
|
||
// Serialize transforms endpoints labels into a external-dns recognizable format string | ||
// withQuotes adds additional quotes | ||
func (l Labels) Serialize(withQuotes bool) string { | ||
var tokens []string | ||
tokens = append(tokens, fmt.Sprintf("heritage=%s", heritage)) | ||
var keys []string | ||
for key := range l { | ||
keys = append(keys, key) | ||
} | ||
sort.Strings(keys) // sort for consistency | ||
|
||
for _, key := range keys { | ||
tokens = append(tokens, fmt.Sprintf("%s/%s=%s", heritage, key, l[key])) | ||
} | ||
if withQuotes { | ||
return fmt.Sprintf("\"%s\"", strings.Join(tokens, ",")) | ||
} | ||
return strings.Join(tokens, ",") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
/* | ||
Copyright 2017 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 endpoint | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/suite" | ||
) | ||
|
||
type LabelsSuite struct { | ||
suite.Suite | ||
foo Labels | ||
fooAsText string | ||
fooAsTextWithQuotes string | ||
barText string | ||
barTextAsMap Labels | ||
noHeritageText string | ||
noHeritageAsMap Labels | ||
wrongHeritageText string | ||
multipleHeritageText string //considered invalid | ||
} | ||
|
||
func (suite *LabelsSuite) SetupTest() { | ||
suite.foo = map[string]string{ | ||
"owner": "foo-owner", | ||
"resource": "foo-resource", | ||
} | ||
suite.fooAsText = "heritage=external-dns,external-dns/owner=foo-owner,external-dns/resource=foo-resource" | ||
suite.fooAsTextWithQuotes = fmt.Sprintf(`"%s"`, suite.fooAsText) | ||
|
||
suite.barTextAsMap = map[string]string{ | ||
"owner": "bar-owner", | ||
"resource": "bar-resource", | ||
"new-key": "bar-new-key", | ||
} | ||
suite.barText = "heritage=external-dns,,external-dns/owner=bar-owner,external-dns/resource=bar-resource,external-dns/new-key=bar-new-key,random=stuff,no-equal-sign,," //also has some random gibberish | ||
|
||
suite.noHeritageText = "external-dns/owner=random-owner" | ||
suite.wrongHeritageText = "heritage=mate,external-dns/owner=random-owner" | ||
suite.multipleHeritageText = "heritage=mate,heritage=external-dns,external-dns/owner=random-owner" | ||
} | ||
|
||
func (suite *LabelsSuite) TestSerialize() { | ||
suite.Equal(suite.fooAsText, suite.foo.Serialize(false), "should serializeLabel") | ||
suite.Equal(suite.fooAsTextWithQuotes, suite.foo.Serialize(true), "should serializeLabel") | ||
} | ||
|
||
func (suite *LabelsSuite) TestDeserialize() { | ||
foo, err := NewLabelsFromString(suite.fooAsText) | ||
suite.NoError(err, "should succeed for valid label text") | ||
suite.Equal(suite.foo, foo, "should reconstruct original label map") | ||
|
||
foo, err = NewLabelsFromString(suite.fooAsTextWithQuotes) | ||
suite.NoError(err, "should succeed for valid label text") | ||
suite.Equal(suite.foo, foo, "should reconstruct original label map") | ||
|
||
bar, err := NewLabelsFromString(suite.barText) | ||
suite.NoError(err, "should succeed for valid label text") | ||
suite.Equal(suite.barTextAsMap, bar, "should reconstruct original label map") | ||
|
||
noHeritage, err := NewLabelsFromString(suite.noHeritageText) | ||
suite.Equal(ErrInvalidHeritage, err, "should fail if no heritage is found") | ||
suite.Nil(noHeritage, "should return nil") | ||
|
||
wrongHeritage, err := NewLabelsFromString(suite.wrongHeritageText) | ||
suite.Equal(ErrInvalidHeritage, err, "should fail if wrong heritage is found") | ||
suite.Nil(wrongHeritage, "if error should return nil") | ||
|
||
multipleHeritage, err := NewLabelsFromString(suite.multipleHeritageText) | ||
suite.Equal(ErrInvalidHeritage, err, "should fail if multiple heritage is found") | ||
suite.Nil(multipleHeritage, "if error should return nil") | ||
} | ||
|
||
func TestLabels(t *testing.T) { | ||
suite.Run(t, new(LabelsSuite)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
/* | ||
Copyright 2017 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 plan | ||
|
||
import ( | ||
"sort" | ||
|
||
"github.com/kubernetes-incubator/external-dns/endpoint" | ||
) | ||
|
||
// ConflictResolver is used to make a decision in case of two or more different kubernetes resources | ||
// are trying to acquire same DNS name | ||
type ConflictResolver interface { | ||
ResolveCreate(candidates []*endpoint.Endpoint) *endpoint.Endpoint | ||
ResolveUpdate(current *endpoint.Endpoint, candidates []*endpoint.Endpoint) *endpoint.Endpoint | ||
} | ||
|
||
// PerResource allows only one resource to own a given dns name | ||
type PerResource struct{} | ||
|
||
// ResolveCreate is invoked when dns name is not owned by any resource | ||
// ResolveCreate takes "minimal" (string comparison of Target) endpoint to acquire the DNS record | ||
func (s PerResource) ResolveCreate(candidates []*endpoint.Endpoint) *endpoint.Endpoint { | ||
var min *endpoint.Endpoint | ||
for _, ep := range candidates { | ||
if min == nil || s.less(ep, min) { | ||
min = ep | ||
} | ||
} | ||
return min | ||
} | ||
|
||
// ResolveUpdate is invoked when dns name is already owned by "current" endpoint | ||
// ResolveUpdate uses "current" record as base and updates it accordingly with new version of same resource | ||
// if it doesn't exist then pick min | ||
func (s PerResource) ResolveUpdate(current *endpoint.Endpoint, candidates []*endpoint.Endpoint) *endpoint.Endpoint { | ||
currentResource := current.Labels[endpoint.ResourceLabelKey] // resource which has already acquired the DNS | ||
// TODO: sort candidates only needed because we can still have two endpoints from same resource here. We sort for consistency | ||
// TODO: remove once single endpoint can have multiple targets | ||
sort.SliceStable(candidates, func(i, j int) bool { | ||
return s.less(candidates[i], candidates[j]) | ||
}) | ||
for _, ep := range candidates { | ||
if ep.Labels[endpoint.ResourceLabelKey] == currentResource { | ||
return ep | ||
} | ||
} | ||
return s.ResolveCreate(candidates) | ||
} | ||
|
||
// less returns true if endpoint x is less than y | ||
func (s PerResource) less(x, y *endpoint.Endpoint) bool { | ||
return x.Target < y.Target | ||
} | ||
|
||
// TODO: with cross-resource/cross-cluster setup alternative variations of ConflictResolver can be used |
Oops, something went wrong.