Replace godep with dep

This commit is contained in:
Manuel de Brito Fontes 2017-10-06 17:26:14 -03:00
parent 1e7489927c
commit bf5616c65b
14883 changed files with 3937406 additions and 361781 deletions

View file

@ -0,0 +1,47 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"dns.go",
"doc.go",
"plugins.go",
],
deps = [
"//federation/pkg/dnsprovider/rrstype:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["dns_test.go"],
library = ":go_default_library",
deps = ["//federation/pkg/dnsprovider/rrstype:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//federation/pkg/dnsprovider/providers/aws/route53:all-srcs",
"//federation/pkg/dnsprovider/providers/coredns:all-srcs",
"//federation/pkg/dnsprovider/providers/google/clouddns:all-srcs",
"//federation/pkg/dnsprovider/rrstype:all-srcs",
"//federation/pkg/dnsprovider/tests:all-srcs",
],
tags = ["automanaged"],
)

View file

@ -0,0 +1,114 @@
/*
Copyright 2016 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 dnsprovider
import (
"reflect"
"k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype"
)
// Interface is an abstract, pluggable interface for DNS providers.
type Interface interface {
// Zones returns the provider's Zones interface, or false if not supported.
Zones() (Zones, bool)
}
type Zones interface {
// List returns the managed Zones, or an error if the list operation failed.
List() ([]Zone, error)
// Add creates and returns a new managed zone, or an error if the operation failed
Add(Zone) (Zone, error)
// Remove deletes a managed zone, or returns an error if the operation failed.
Remove(Zone) error
// New allocates a new Zone, which can then be passed to Add()
// Arguments are as per the Zone interface below.
New(name string) (Zone, error)
}
type Zone interface {
// Name returns the name of the zone, e.g. "example.com"
Name() string
// ID returns the unique provider identifier for the zone
ID() string
// ResourceRecordSets returns the provider's ResourceRecordSets interface, or false if not supported.
ResourceRecordSets() (ResourceRecordSets, bool)
}
type ResourceRecordSets interface {
// List returns the ResourceRecordSets of the Zone, or an error if the list operation failed.
List() ([]ResourceRecordSet, error)
// Get returns the ResourceRecordSet list with the name in the Zone.
// This is a list because there might be multiple records of different
// types for a given name. If the named resource record sets do not
// exist, but no error occurred, the returned record set will be empty
// and error will be nil.
Get(name string) ([]ResourceRecordSet, error)
// New allocates a new ResourceRecordSet, which can then be passed to ResourceRecordChangeset Add() or Remove()
// Arguments are as per the ResourceRecordSet interface below.
New(name string, rrdatas []string, ttl int64, rrstype rrstype.RrsType) ResourceRecordSet
// StartChangeset begins a new batch operation of changes against the Zone
StartChangeset() ResourceRecordChangeset
// Zone returns the parent zone
Zone() Zone
}
// ResourceRecordChangeset accumulates a set of changes, that can then be applied with Apply
type ResourceRecordChangeset interface {
// Add adds the creation of a ResourceRecordSet in the Zone to the changeset
Add(ResourceRecordSet) ResourceRecordChangeset
// Remove adds the removal of a ResourceRecordSet in the Zone to the changeset
// The supplied ResourceRecordSet must match one of the existing recordsets (obtained via List()) exactly.
Remove(ResourceRecordSet) ResourceRecordChangeset
// Upsert adds an "create or update" operation for the ResourceRecordSet in the Zone to the changeset
// Note: the implementation may translate this into a Remove followed by an Add operation.
// If you have the pre-image, it will likely be more efficient to call Remove and Add.
Upsert(ResourceRecordSet) ResourceRecordChangeset
// Apply applies the accumulated operations to the Zone.
Apply() error
// IsEmpty returns true if there are no accumulated operations.
IsEmpty() bool
// ResourceRecordSets returns the parent ResourceRecordSets
ResourceRecordSets() ResourceRecordSets
}
type ResourceRecordSet interface {
// Name returns the name of the ResourceRecordSet, e.g. "www.example.com".
Name() string
// Rrdatas returns the Resource Record Datas of the record set.
Rrdatas() []string
// Ttl returns the time-to-live of the record set, in seconds.
Ttl() int64
// Type returns the type of the record set (A, CNAME, SRV, etc)
Type() rrstype.RrsType
}
/* ResourceRecordSetsEquivalent compares two ResourceRecordSets for semantic equivalence.
Go's equality operator doesn't work the way we want it to in this case,
hence the need for this function.
More specifically (from the Go spec):
"Two struct values are equal if their corresponding non-blank fields are equal."
In our case, there may be some private internal member variables that may not be not equal,
but we want the two structs to be considered equivalent anyway, if the fields exposed
via their interfaces are equal.
*/
func ResourceRecordSetsEquivalent(r1, r2 ResourceRecordSet) bool {
if r1.Name() == r2.Name() && reflect.DeepEqual(r1.Rrdatas(), r2.Rrdatas()) && r1.Ttl() == r2.Ttl() && r1.Type() == r2.Type() {
return true
}
return false
}

View file

@ -0,0 +1,96 @@
/*
Copyright 2016 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 dnsprovider
import (
"testing"
"k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype"
)
// Compile time interface check
var _ ResourceRecordSet = record{}
type record struct {
name string
rrdatas []string
ttl int64
type_ string
}
func (r record) Name() string {
return r.name
}
func (r record) Ttl() int64 {
return r.ttl
}
func (r record) Rrdatas() []string {
return r.rrdatas
}
func (r record) Type() rrstype.RrsType {
return rrstype.RrsType(r.type_)
}
const testDNSZone string = "foo.com"
var testData = []struct {
inputs [2]record
expectedOutput bool
}{
{
[2]record{
{"foo", []string{"1.2.3.4", "5,6,7,8"}, 180, "A"}, // Identical
{"foo", []string{"1.2.3.4", "5,6,7,8"}, 180, "A"}}, true,
},
{
[2]record{
{"foo", []string{"1.2.3.4", "5,6,7,8"}, 180, "A"}, // Identical except Name
{"bar", []string{"1.2.3.4", "5,6,7,8"}, 180, "A"}}, false,
},
{
[2]record{
{"foo", []string{"1.2.3.4", "5,6,7,8"}, 180, "A"}, // Identical except Rrdata
{"foo", []string{"1.2.3.4", "5,6,7,9"}, 180, "A"}}, false,
},
{
[2]record{
{"foo", []string{"1.2.3.4", "5,6,7,8"}, 180, "A"}, // Identical except Rrdata ordering reversed
{"foo", []string{"5,6,7,8", "1.2.3.4"}, 180, "A"}}, false,
},
{
[2]record{
{"foo", []string{"1.2.3.4", "5,6,7,8"}, 180, "A"}, // Identical except TTL
{"foo", []string{"1.2.3.4", "5,6,7,8"}, 150, "A"}}, false,
},
{
[2]record{
{"foo", []string{"1.2.3.4", "5,6,7,8"}, 180, "A"}, // Identical except Type
{"foo", []string{"1.2.3.4", "5,6,7,8"}, 180, "CNAME"}}, false,
},
}
func TestEquivalent(t *testing.T) {
for _, test := range testData {
output := ResourceRecordSetsEquivalent(test.inputs[0], test.inputs[1])
if output != test.expectedOutput {
t.Errorf("Expected equivalence comparison of %q and %q to yield %v, but it vielded %v", test.inputs[0], test.inputs[1], test.expectedOutput, output)
}
}
}

View file

@ -0,0 +1,21 @@
/*
Copyright 2016 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.
*/
/*
dnsprovider supplies interfaces for dns service providers (e.g. Google Cloud DNS, AWS route53, etc).
Implementations exist in the providers sub-package
*/
package dnsprovider // import "k8s.io/kubernetes/federation/pkg/dnsprovider"

View file

@ -0,0 +1,109 @@
/*
Copyright 2016 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 dnsprovider
import (
"fmt"
"io"
"os"
"sync"
"github.com/golang/glog"
)
// Factory is a function that returns a dnsprovider.Interface.
// The config parameter provides an io.Reader handler to the factory in
// order to load specific configurations. If no configuration is provided
// the parameter is nil.
type Factory func(config io.Reader) (Interface, error)
// All registered dns providers.
var providersMutex sync.Mutex
var providers = make(map[string]Factory)
// RegisterDnsProvider registers a dnsprovider.Factory by name. This
// is expected to happen during startup.
func RegisterDnsProvider(name string, cloud Factory) {
providersMutex.Lock()
defer providersMutex.Unlock()
if _, found := providers[name]; found {
glog.Fatalf("DNS provider %q was registered twice", name)
}
glog.V(1).Infof("Registered DNS provider %q", name)
providers[name] = cloud
}
// GetDnsProvider creates an instance of the named DNS provider, or nil if
// the name is not known. The error return is only used if the named provider
// was known but failed to initialize. The config parameter specifies the
// io.Reader handler of the configuration file for the DNS provider, or nil
// for no configuration.
func GetDnsProvider(name string, config io.Reader) (Interface, error) {
providersMutex.Lock()
defer providersMutex.Unlock()
f, found := providers[name]
if !found {
return nil, nil
}
return f(config)
}
// Returns a list of registered dns providers.
func RegisteredDnsProviders() []string {
registeredProviders := make([]string, len(providers))
i := 0
for provider := range providers {
registeredProviders[i] = provider
i = i + 1
}
return registeredProviders
}
// InitDnsProvider creates an instance of the named DNS provider.
func InitDnsProvider(name string, configFilePath string) (Interface, error) {
var dns Interface
var err error
if name == "" {
glog.Info("No DNS provider specified.")
return nil, nil
}
if configFilePath != "" {
var config *os.File
config, err = os.Open(configFilePath)
if err != nil {
return nil, fmt.Errorf("Couldn't open DNS provider configuration %s: %#v", configFilePath, err)
}
defer config.Close()
dns, err = GetDnsProvider(name, config)
} else {
// Pass explicit nil so plugins can actually check for nil. See
// "Why is my nil error value not equal to nil?" in golang.org/doc/faq.
dns, err = GetDnsProvider(name, nil)
}
if err != nil {
return nil, fmt.Errorf("could not init DNS provider %q: %v", name, err)
}
if dns == nil {
return nil, fmt.Errorf("unknown DNS provider %q", name)
}
return dns, nil
}

View file

@ -0,0 +1,61 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"interface.go",
"route53.go",
"rrchangeset.go",
"rrset.go",
"rrsets.go",
"zone.go",
"zones.go",
],
deps = [
"//federation/pkg/dnsprovider:go_default_library",
"//federation/pkg/dnsprovider/providers/aws/route53/stubs:go_default_library",
"//federation/pkg/dnsprovider/rrstype:go_default_library",
"//vendor/github.com/aws/aws-sdk-go/aws:go_default_library",
"//vendor/github.com/aws/aws-sdk-go/aws/request:go_default_library",
"//vendor/github.com/aws/aws-sdk-go/aws/session:go_default_library",
"//vendor/github.com/aws/aws-sdk-go/service/route53:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["route53_test.go"],
library = ":go_default_library",
deps = [
"//federation/pkg/dnsprovider:go_default_library",
"//federation/pkg/dnsprovider/providers/aws/route53/stubs:go_default_library",
"//federation/pkg/dnsprovider/rrstype:go_default_library",
"//federation/pkg/dnsprovider/tests:go_default_library",
"//vendor/github.com/aws/aws-sdk-go/aws:go_default_library",
"//vendor/github.com/aws/aws-sdk-go/service/route53:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//federation/pkg/dnsprovider/providers/aws/route53/stubs:all-srcs",
],
tags = ["automanaged"],
)

View file

@ -0,0 +1,39 @@
/*
Copyright 2016 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 route53
import (
"k8s.io/kubernetes/federation/pkg/dnsprovider"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/aws/route53/stubs"
)
// Compile time check for interface adherence
var _ dnsprovider.Interface = Interface{}
type Interface struct {
service stubs.Route53API
}
// New builds an Interface, with a specified Route53API implementation.
// This is useful for testing purposes, but also if we want an instance with with custom AWS options.
func New(service stubs.Route53API) *Interface {
return &Interface{service}
}
func (i Interface) Zones() (zones dnsprovider.Zones, supported bool) {
return Zones{&i}, true
}

View file

@ -0,0 +1,72 @@
/*
Copyright 2016 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.
*/
// route53 is the implementation of pkg/dnsprovider interface for AWS Route53
package route53
import (
"io"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/route53"
"github.com/golang/glog"
"k8s.io/kubernetes/federation/pkg/dnsprovider"
)
const (
ProviderName = "aws-route53"
)
func init() {
dnsprovider.RegisterDnsProvider(ProviderName, func(config io.Reader) (dnsprovider.Interface, error) {
return newRoute53(config)
})
}
// route53HandlerLogger is a request handler for aws-sdk-go that logs route53 requests
func route53HandlerLogger(req *request.Request) {
service := req.ClientInfo.ServiceName
name := "?"
if req.Operation != nil {
name = req.Operation.Name
}
glog.V(4).Infof("AWS request: %s %s", service, name)
}
// newRoute53 creates a new instance of an AWS Route53 DNS Interface.
func newRoute53(config io.Reader) (*Interface, error) {
// Connect to AWS Route53 - TODO: Do more sophisticated auth
awsConfig := aws.NewConfig()
// This avoids a confusing error message when we fail to get credentials
// e.g. https://github.com/kubernetes/kops/issues/605
awsConfig = awsConfig.WithCredentialsChainVerboseErrors(true)
svc := route53.New(session.New(), awsConfig)
// Add our handler that will log requests
svc.Handlers.Sign.PushFrontNamed(request.NamedHandler{
Name: "k8s/logger",
Fn: route53HandlerLogger,
})
return New(svc), nil
}

View file

@ -0,0 +1,295 @@
/*
Copyright 2016 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 route53
import (
"flag"
"fmt"
"os"
"testing"
"k8s.io/kubernetes/federation/pkg/dnsprovider"
route53testing "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/aws/route53/stubs"
"k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/route53"
"k8s.io/kubernetes/federation/pkg/dnsprovider/tests"
)
func newTestInterface() (dnsprovider.Interface, error) {
// Use this to test the real cloud service.
// return dnsprovider.GetDnsProvider(ProviderName, strings.NewReader("\n[global]\nproject-id = federation0-cluster00"))
return newFakeInterface() // Use this to stub out the entire cloud service
}
func newFakeInterface() (dnsprovider.Interface, error) {
var service route53testing.Route53API
service = route53testing.NewRoute53APIStub()
iface := New(service)
// Add a fake zone to test against.
params := &route53.CreateHostedZoneInput{
CallerReference: aws.String("Nonce"), // Required
Name: aws.String("example.com"), // Required
}
_, err := iface.service.CreateHostedZone(params)
if err != nil {
return nil, err
}
return iface, nil
}
var interface_ dnsprovider.Interface
func TestMain(m *testing.M) {
fmt.Printf("Parsing flags.\n")
flag.Parse()
var err error
fmt.Printf("Getting new test interface.\n")
interface_, err = newTestInterface()
if err != nil {
fmt.Printf("Error creating interface: %v", err)
os.Exit(1)
}
fmt.Printf("Running tests...\n")
os.Exit(m.Run())
}
// zones returns the zones interface for the configured dns provider account/project,
// or fails if it can't be found
func zones(t *testing.T) dnsprovider.Zones {
zonesInterface, supported := interface_.Zones()
if !supported {
t.Fatalf("Zones interface not supported by interface %v", interface_)
} else {
t.Logf("Got zones %v\n", zonesInterface)
}
return zonesInterface
}
// firstZone returns the first zone for the configured dns provider account/project,
// or fails if it can't be found
func firstZone(t *testing.T) dnsprovider.Zone {
t.Logf("Getting zones")
z := zones(t)
zones, err := z.List()
if err != nil {
t.Fatalf("Failed to list zones: %v", err)
} else {
t.Logf("Got zone list: %v\n", zones)
}
if len(zones) < 1 {
t.Fatalf("Zone listing returned %d, expected >= %d", len(zones), 1)
} else {
t.Logf("Got at least 1 zone in list:%v\n", zones[0])
}
return zones[0]
}
/* rrs returns the ResourceRecordSets interface for a given zone */
func rrs(t *testing.T, zone dnsprovider.Zone) (r dnsprovider.ResourceRecordSets) {
rrsets, supported := zone.ResourceRecordSets()
if !supported {
t.Fatalf("ResourceRecordSets interface not supported by zone %v", zone)
return r
}
return rrsets
}
func listRrsOrFail(t *testing.T, rrsets dnsprovider.ResourceRecordSets) []dnsprovider.ResourceRecordSet {
rrset, err := rrsets.List()
if err != nil {
t.Fatalf("Failed to list recordsets: %v", err)
} else {
if len(rrset) < 0 {
t.Fatalf("Record set length=%d, expected >=0", len(rrset))
} else {
t.Logf("Got %d recordsets: %v", len(rrset), rrset)
}
}
return rrset
}
func getExampleRrs(zone dnsprovider.Zone) dnsprovider.ResourceRecordSet {
rrsets, _ := zone.ResourceRecordSets()
return rrsets.New("www11."+zone.Name(), []string{"10.10.10.10", "169.20.20.20"}, 180, rrstype.A)
}
func getInvalidRrs(zone dnsprovider.Zone) dnsprovider.ResourceRecordSet {
rrsets, _ := zone.ResourceRecordSets()
return rrsets.New("www12."+zone.Name(), []string{"rubbish", "rubbish"}, 180, rrstype.A)
}
func addRrsetOrFail(t *testing.T, rrsets dnsprovider.ResourceRecordSets, rrset dnsprovider.ResourceRecordSet) {
err := rrsets.StartChangeset().Add(rrset).Apply()
if err != nil {
t.Fatalf("Failed to add recordsets: %v", err)
}
}
/* TestZonesList verifies that listing of zones succeeds */
func TestZonesList(t *testing.T) {
firstZone(t)
}
/* TestZonesID verifies that the id of the zone is returned with the prefix removed */
func TestZonesID(t *testing.T) {
zone := firstZone(t)
// Check /hostedzone/ prefix is removed
zoneID := zone.ID()
if zoneID != zone.Name() {
t.Fatalf("Unexpected zone id: %q", zoneID)
}
}
/* TestZoneAddSuccess verifies that addition of a valid managed DNS zone succeeds */
func TestZoneAddSuccess(t *testing.T) {
testZoneName := "ubernetes.testing"
z := zones(t)
input, err := z.New(testZoneName)
if err != nil {
t.Errorf("Failed to allocate new zone object %s: %v", testZoneName, err)
}
zone, err := z.Add(input)
if err != nil {
t.Errorf("Failed to create new managed DNS zone %s: %v", testZoneName, err)
}
defer func(zone dnsprovider.Zone) {
if zone != nil {
if err := z.Remove(zone); err != nil {
t.Errorf("Failed to delete zone %v: %v", zone, err)
}
}
}(zone)
t.Logf("Successfully added managed DNS zone: %v", zone)
}
/* TestResourceRecordSetsList verifies that listing of RRS's succeeds */
func TestResourceRecordSetsList(t *testing.T) {
listRrsOrFail(t, rrs(t, firstZone(t)))
}
/* TestResourceRecordSetsAddSuccess verifies that addition of a valid RRS succeeds */
func TestResourceRecordSetsAddSuccess(t *testing.T) {
zone := firstZone(t)
sets := rrs(t, zone)
set := getExampleRrs(zone)
addRrsetOrFail(t, sets, set)
defer sets.StartChangeset().Remove(set).Apply()
t.Logf("Successfully added resource record set: %v", set)
}
/* TestResourceRecordSetsAdditionVisible verifies that added RRS is visible after addition */
func TestResourceRecordSetsAdditionVisible(t *testing.T) {
zone := firstZone(t)
sets := rrs(t, zone)
rrset := getExampleRrs(zone)
addRrsetOrFail(t, sets, rrset)
defer sets.StartChangeset().Remove(rrset).Apply()
t.Logf("Successfully added resource record set: %v", rrset)
found := false
for _, record := range listRrsOrFail(t, sets) {
if record.Name() == rrset.Name() {
found = true
break
}
}
if !found {
t.Errorf("Failed to find added resource record set %s", rrset.Name())
}
}
/* TestResourceRecordSetsAddDuplicateFail verifies that addition of a duplicate RRS fails */
func TestResourceRecordSetsAddDuplicateFail(t *testing.T) {
zone := firstZone(t)
sets := rrs(t, zone)
rrset := getExampleRrs(zone)
addRrsetOrFail(t, sets, rrset)
defer sets.StartChangeset().Remove(rrset).Apply()
t.Logf("Successfully added resource record set: %v", rrset)
// Try to add it again, and verify that the call fails.
err := sets.StartChangeset().Add(rrset).Apply()
if err == nil {
defer sets.StartChangeset().Remove(rrset).Apply()
t.Errorf("Should have failed to add duplicate resource record %v, but succeeded instead.", rrset)
} else {
t.Logf("Correctly failed to add duplicate resource record %v: %v", rrset, err)
}
}
/* TestResourceRecordSetsRemove verifies that the removal of an existing RRS succeeds */
func TestResourceRecordSetsRemove(t *testing.T) {
zone := firstZone(t)
sets := rrs(t, zone)
rrset := getExampleRrs(zone)
addRrsetOrFail(t, sets, rrset)
err := sets.StartChangeset().Remove(rrset).Apply()
if err != nil {
// Try again to clean up.
defer sets.StartChangeset().Remove(rrset).Apply()
t.Errorf("Failed to remove resource record set %v after adding", rrset)
} else {
t.Logf("Successfully removed resource set %v after adding", rrset)
}
}
/* TestResourceRecordSetsRemoveGone verifies that a removed RRS no longer exists */
func TestResourceRecordSetsRemoveGone(t *testing.T) {
zone := firstZone(t)
sets := rrs(t, zone)
rrset := getExampleRrs(zone)
addRrsetOrFail(t, sets, rrset)
err := sets.StartChangeset().Remove(rrset).Apply()
if err != nil {
// Try again to clean up.
defer sets.StartChangeset().Remove(rrset).Apply()
t.Errorf("Failed to remove resource record set %v after adding", rrset)
} else {
t.Logf("Successfully removed resource set %v after adding", rrset)
}
// Check that it's gone
list := listRrsOrFail(t, sets)
found := false
for _, set := range list {
if set.Name() == rrset.Name() {
found = true
break
}
}
if found {
t.Errorf("Deleted resource record set %v is still present", rrset)
}
}
/* TestResourceRecordSetsReplace verifies that replacing an RRS works */
func TestResourceRecordSetsReplace(t *testing.T) {
zone := firstZone(t)
tests.CommonTestResourceRecordSetsReplace(t, zone)
}
/* TestResourceRecordSetsReplaceAll verifies that we can remove an RRS and create one with a different name*/
func TestResourceRecordSetsReplaceAll(t *testing.T) {
zone := firstZone(t)
tests.CommonTestResourceRecordSetsReplaceAll(t, zone)
}
/* TestResourceRecordSetsDifferentTypes verifies that we can add records of the same name but different types */
func TestResourceRecordSetsDifferentTypes(t *testing.T) {
zone := firstZone(t)
tests.CommonTestResourceRecordSetsDifferentTypes(t, zone)
}

View file

@ -0,0 +1,134 @@
/*
Copyright 2016 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 route53
import (
"bytes"
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/route53"
"github.com/golang/glog"
"k8s.io/kubernetes/federation/pkg/dnsprovider"
)
// Compile time check for interface adherence
var _ dnsprovider.ResourceRecordChangeset = &ResourceRecordChangeset{}
type ResourceRecordChangeset struct {
zone *Zone
rrsets *ResourceRecordSets
additions []dnsprovider.ResourceRecordSet
removals []dnsprovider.ResourceRecordSet
upserts []dnsprovider.ResourceRecordSet
}
func (c *ResourceRecordChangeset) Add(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset {
c.additions = append(c.additions, rrset)
return c
}
func (c *ResourceRecordChangeset) Remove(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset {
c.removals = append(c.removals, rrset)
return c
}
func (c *ResourceRecordChangeset) Upsert(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset {
c.upserts = append(c.upserts, rrset)
return c
}
// buildChange converts a dnsprovider.ResourceRecordSet to a route53.Change request
func buildChange(action string, rrs dnsprovider.ResourceRecordSet) *route53.Change {
change := &route53.Change{
Action: aws.String(action),
ResourceRecordSet: &route53.ResourceRecordSet{
Name: aws.String(rrs.Name()),
Type: aws.String(string(rrs.Type())),
TTL: aws.Int64(rrs.Ttl()),
},
}
for _, rrdata := range rrs.Rrdatas() {
rr := &route53.ResourceRecord{
Value: aws.String(rrdata),
}
change.ResourceRecordSet.ResourceRecords = append(change.ResourceRecordSet.ResourceRecords, rr)
}
return change
}
func (c *ResourceRecordChangeset) Apply() error {
hostedZoneID := c.zone.impl.Id
var changes []*route53.Change
for _, removal := range c.removals {
change := buildChange(route53.ChangeActionDelete, removal)
changes = append(changes, change)
}
for _, addition := range c.additions {
change := buildChange(route53.ChangeActionCreate, addition)
changes = append(changes, change)
}
for _, upsert := range c.upserts {
change := buildChange(route53.ChangeActionUpsert, upsert)
changes = append(changes, change)
}
if len(changes) == 0 {
return nil
}
if glog.V(8) {
var sb bytes.Buffer
for _, change := range changes {
sb.WriteString(fmt.Sprintf("\t%s %s %s\n", aws.StringValue(change.Action), aws.StringValue(change.ResourceRecordSet.Type), aws.StringValue(change.ResourceRecordSet.Name)))
}
glog.V(8).Infof("Route53 Changeset:\n%s", sb.String())
}
service := c.zone.zones.interface_.service
request := &route53.ChangeResourceRecordSetsInput{
ChangeBatch: &route53.ChangeBatch{
Changes: changes,
},
HostedZoneId: hostedZoneID,
}
_, err := service.ChangeResourceRecordSets(request)
if err != nil {
// Cast err to awserr.Error to get the Code and
// Message from an error.
return err
}
return nil
}
func (c *ResourceRecordChangeset) IsEmpty() bool {
return len(c.removals) == 0 && len(c.additions) == 0
}
// ResourceRecordSets returns the parent ResourceRecordSets
func (c *ResourceRecordChangeset) ResourceRecordSets() dnsprovider.ResourceRecordSets {
return c.rrsets
}

View file

@ -0,0 +1,63 @@
/*
Copyright 2016 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 route53
import (
"k8s.io/kubernetes/federation/pkg/dnsprovider"
"k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/route53"
)
// Compile time check for interface adherence
var _ dnsprovider.ResourceRecordSet = ResourceRecordSet{}
type ResourceRecordSet struct {
impl *route53.ResourceRecordSet
rrsets *ResourceRecordSets
}
func (rrset ResourceRecordSet) Name() string {
return aws.StringValue(rrset.impl.Name)
}
func (rrset ResourceRecordSet) Rrdatas() []string {
// Sigh - need to unpack the strings out of the route53 ResourceRecords
result := make([]string, len(rrset.impl.ResourceRecords))
for i, record := range rrset.impl.ResourceRecords {
result[i] = aws.StringValue(record.Value)
}
return result
}
func (rrset ResourceRecordSet) Ttl() int64 {
return aws.Int64Value(rrset.impl.TTL)
}
func (rrset ResourceRecordSet) Type() rrstype.RrsType {
return rrstype.RrsType(aws.StringValue(rrset.impl.Type))
}
// Route53ResourceRecordSet returns the route53 ResourceRecordSet object for the ResourceRecordSet
// This is a "back door" that allows for limited access to the ResourceRecordSet,
// without having to requery it, so that we can expose AWS specific functionality.
// Using this method should be avoided where possible; instead prefer to add functionality
// to the cross-provider ResourceRecordSet interface.
func (rrset ResourceRecordSet) Route53ResourceRecordSet() *route53.ResourceRecordSet {
return rrset.impl
}

View file

@ -0,0 +1,106 @@
/*
Copyright 2016 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 route53
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/route53"
"k8s.io/kubernetes/federation/pkg/dnsprovider"
"k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype"
)
// Compile time check for interface adherence
var _ dnsprovider.ResourceRecordSets = ResourceRecordSets{}
type ResourceRecordSets struct {
zone *Zone
}
func (rrsets ResourceRecordSets) List() ([]dnsprovider.ResourceRecordSet, error) {
input := route53.ListResourceRecordSetsInput{
HostedZoneId: rrsets.zone.impl.Id,
}
var list []dnsprovider.ResourceRecordSet
err := rrsets.zone.zones.interface_.service.ListResourceRecordSetsPages(&input, func(page *route53.ListResourceRecordSetsOutput, lastPage bool) bool {
for _, rrset := range page.ResourceRecordSets {
list = append(list, &ResourceRecordSet{rrset, &rrsets})
}
return true
})
if err != nil {
return nil, err
}
return list, nil
}
func (rrsets ResourceRecordSets) Get(name string) ([]dnsprovider.ResourceRecordSet, error) {
// This list implementation is very similar to the one implemented in
// the List() method above, but it restricts the retrieved list to
// the records whose name match the given `name`.
input := route53.ListResourceRecordSetsInput{
HostedZoneId: rrsets.zone.impl.Id,
StartRecordName: aws.String(name),
}
var list []dnsprovider.ResourceRecordSet
err := rrsets.zone.zones.interface_.service.ListResourceRecordSetsPages(&input, func(page *route53.ListResourceRecordSetsOutput, lastPage bool) bool {
for _, rrset := range page.ResourceRecordSets {
if aws.StringValue(rrset.Name) != name {
return false
}
list = append(list, &ResourceRecordSet{rrset, &rrsets})
}
return true
})
if err != nil {
return nil, err
}
return list, nil
}
func (r ResourceRecordSets) StartChangeset() dnsprovider.ResourceRecordChangeset {
return &ResourceRecordChangeset{
zone: r.zone,
rrsets: &r,
}
}
func (r ResourceRecordSets) New(name string, rrdatas []string, ttl int64, rrstype rrstype.RrsType) dnsprovider.ResourceRecordSet {
rrstypeStr := string(rrstype)
rrs := &route53.ResourceRecordSet{
Name: &name,
Type: &rrstypeStr,
TTL: &ttl,
}
for _, rrdata := range rrdatas {
rrs.ResourceRecords = append(rrs.ResourceRecords, &route53.ResourceRecord{
Value: aws.String(rrdata),
})
}
return ResourceRecordSet{
rrs,
&r,
}
}
// Zone returns the parent zone
func (rrset ResourceRecordSets) Zone() dnsprovider.Zone {
return rrset.zone
}

View file

@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["route53api.go"],
deps = [
"//vendor/github.com/aws/aws-sdk-go/aws:go_default_library",
"//vendor/github.com/aws/aws-sdk-go/service/route53:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,133 @@
/*
Copyright 2016 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.
*/
/* internal implements a stub for the AWS Route53 API, used primarily for unit testing purposes */
package stubs
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/route53"
)
// Compile time check for interface conformance
var _ Route53API = &Route53APIStub{}
/* Route53API is the subset of the AWS Route53 API that we actually use. Add methods as required. Signatures must match exactly. */
type Route53API interface {
ListResourceRecordSetsPages(input *route53.ListResourceRecordSetsInput, fn func(p *route53.ListResourceRecordSetsOutput, lastPage bool) (shouldContinue bool)) error
ChangeResourceRecordSets(*route53.ChangeResourceRecordSetsInput) (*route53.ChangeResourceRecordSetsOutput, error)
ListHostedZonesPages(input *route53.ListHostedZonesInput, fn func(p *route53.ListHostedZonesOutput, lastPage bool) (shouldContinue bool)) error
CreateHostedZone(*route53.CreateHostedZoneInput) (*route53.CreateHostedZoneOutput, error)
DeleteHostedZone(*route53.DeleteHostedZoneInput) (*route53.DeleteHostedZoneOutput, error)
}
// Route53APIStub is a minimal implementation of Route53API, used primarily for unit testing.
// See http://http://docs.aws.amazon.com/sdk-for-go/api/service/route53.html for descriptions
// of all of its methods.
type Route53APIStub struct {
zones map[string]*route53.HostedZone
recordSets map[string]map[string][]*route53.ResourceRecordSet
}
// NewRoute53APIStub returns an initialized Route53APIStub
func NewRoute53APIStub() *Route53APIStub {
return &Route53APIStub{
zones: make(map[string]*route53.HostedZone),
recordSets: make(map[string]map[string][]*route53.ResourceRecordSet),
}
}
func (r *Route53APIStub) ListResourceRecordSetsPages(input *route53.ListResourceRecordSetsInput, fn func(p *route53.ListResourceRecordSetsOutput, lastPage bool) (shouldContinue bool)) error {
output := route53.ListResourceRecordSetsOutput{} // TODO: Support optional input args.
if len(r.recordSets) <= 0 {
output.ResourceRecordSets = []*route53.ResourceRecordSet{}
} else if _, ok := r.recordSets[*input.HostedZoneId]; !ok {
output.ResourceRecordSets = []*route53.ResourceRecordSet{}
} else {
for _, rrsets := range r.recordSets[*input.HostedZoneId] {
for _, rrset := range rrsets {
output.ResourceRecordSets = append(output.ResourceRecordSets, rrset)
}
}
}
lastPage := true
fn(&output, lastPage)
return nil
}
func (r *Route53APIStub) ChangeResourceRecordSets(input *route53.ChangeResourceRecordSetsInput) (*route53.ChangeResourceRecordSetsOutput, error) {
output := &route53.ChangeResourceRecordSetsOutput{}
recordSets, ok := r.recordSets[*input.HostedZoneId]
if !ok {
recordSets = make(map[string][]*route53.ResourceRecordSet)
}
for _, change := range input.ChangeBatch.Changes {
key := *change.ResourceRecordSet.Name + "::" + *change.ResourceRecordSet.Type
switch *change.Action {
case route53.ChangeActionCreate:
if _, found := recordSets[key]; found {
return nil, fmt.Errorf("Attempt to create duplicate rrset %s", key) // TODO: Return AWS errors with codes etc
}
recordSets[key] = append(recordSets[key], change.ResourceRecordSet)
case route53.ChangeActionDelete:
if _, found := recordSets[key]; !found {
return nil, fmt.Errorf("Attempt to delete non-existent rrset %s", key) // TODO: Check other fields too
}
delete(recordSets, key)
case route53.ChangeActionUpsert:
// TODO - not used yet
}
}
r.recordSets[*input.HostedZoneId] = recordSets
return output, nil // TODO: We should ideally return status etc, but we don't' use that yet.
}
func (r *Route53APIStub) ListHostedZonesPages(input *route53.ListHostedZonesInput, fn func(p *route53.ListHostedZonesOutput, lastPage bool) (shouldContinue bool)) error {
output := &route53.ListHostedZonesOutput{}
for _, zone := range r.zones {
output.HostedZones = append(output.HostedZones, zone)
}
lastPage := true
fn(output, lastPage)
return nil
}
func (r *Route53APIStub) CreateHostedZone(input *route53.CreateHostedZoneInput) (*route53.CreateHostedZoneOutput, error) {
name := aws.StringValue(input.Name)
id := "/hostedzone/" + name
if _, ok := r.zones[id]; ok {
return nil, fmt.Errorf("Error creating hosted DNS zone: %s already exists", id)
}
r.zones[id] = &route53.HostedZone{
Id: aws.String(id),
Name: aws.String(name),
}
return &route53.CreateHostedZoneOutput{HostedZone: r.zones[id]}, nil
}
func (r *Route53APIStub) DeleteHostedZone(input *route53.DeleteHostedZoneInput) (*route53.DeleteHostedZoneOutput, error) {
if _, ok := r.zones[*input.Id]; !ok {
return nil, fmt.Errorf("Error deleting hosted DNS zone: %s does not exist", *input.Id)
}
if len(r.recordSets[*input.Id]) > 0 {
return nil, fmt.Errorf("Error deleting hosted DNS zone: %s has resource records", *input.Id)
}
delete(r.zones, *input.Id)
return &route53.DeleteHostedZoneOutput{}, nil
}

View file

@ -0,0 +1,56 @@
/*
Copyright 2016 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 route53
import (
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/route53"
"k8s.io/kubernetes/federation/pkg/dnsprovider"
)
// Compile time check for interface adherence
var _ dnsprovider.Zone = &Zone{}
type Zone struct {
impl *route53.HostedZone
zones *Zones
}
func (zone *Zone) Name() string {
return aws.StringValue(zone.impl.Name)
}
func (zone *Zone) ID() string {
id := aws.StringValue(zone.impl.Id)
id = strings.TrimPrefix(id, "/hostedzone/")
return id
}
func (zone *Zone) ResourceRecordSets() (dnsprovider.ResourceRecordSets, bool) {
return &ResourceRecordSets{zone}, true
}
// Route53HostedZone returns the route53 HostedZone object for the zone.
// This is a "back door" that allows for limited access to the HostedZone,
// without having to requery it, so that we can expose AWS specific functionality.
// Using this method should be avoided where possible; instead prefer to add functionality
// to the cross-provider Zone interface.
func (zone *Zone) Route53HostedZone() *route53.HostedZone {
return zone.impl
}

View file

@ -0,0 +1,73 @@
/*
Copyright 2016 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 route53
import (
"github.com/aws/aws-sdk-go/service/route53"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/kubernetes/federation/pkg/dnsprovider"
)
// Compile time check for interface adherence
var _ dnsprovider.Zones = Zones{}
type Zones struct {
interface_ *Interface
}
func (zones Zones) List() ([]dnsprovider.Zone, error) {
var zoneList []dnsprovider.Zone
input := route53.ListHostedZonesInput{}
err := zones.interface_.service.ListHostedZonesPages(&input, func(page *route53.ListHostedZonesOutput, lastPage bool) bool {
for _, zone := range page.HostedZones {
zoneList = append(zoneList, &Zone{zone, &zones})
}
return true
})
if err != nil {
return []dnsprovider.Zone{}, err
}
return zoneList, nil
}
func (zones Zones) Add(zone dnsprovider.Zone) (dnsprovider.Zone, error) {
dnsName := zone.Name()
callerReference := string(uuid.NewUUID())
input := route53.CreateHostedZoneInput{Name: &dnsName, CallerReference: &callerReference}
output, err := zones.interface_.service.CreateHostedZone(&input)
if err != nil {
return nil, err
}
return &Zone{output.HostedZone, &zones}, nil
}
func (zones Zones) Remove(zone dnsprovider.Zone) error {
zoneId := zone.(*Zone).impl.Id
input := route53.DeleteHostedZoneInput{Id: zoneId}
_, err := zones.interface_.service.DeleteHostedZone(&input)
if err != nil {
return err
}
return nil
}
func (zones Zones) New(name string) (dnsprovider.Zone, error) {
id := string(uuid.NewUUID())
managedZone := route53.HostedZone{Id: &id, Name: &name}
return &Zone{&managedZone, &zones}, nil
}

View file

@ -0,0 +1,58 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"coredns.go",
"interface.go",
"rrchangeset.go",
"rrset.go",
"rrsets.go",
"zone.go",
"zones.go",
],
deps = [
"//federation/pkg/dnsprovider:go_default_library",
"//federation/pkg/dnsprovider/providers/coredns/stubs:go_default_library",
"//federation/pkg/dnsprovider/rrstype:go_default_library",
"//vendor/github.com/coreos/etcd/client:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/miekg/coredns/middleware/etcd/msg:go_default_library",
"//vendor/golang.org/x/net/context:go_default_library",
"//vendor/gopkg.in/gcfg.v1:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["coredns_test.go"],
library = ":go_default_library",
deps = [
"//federation/pkg/dnsprovider:go_default_library",
"//federation/pkg/dnsprovider/providers/coredns/stubs:go_default_library",
"//federation/pkg/dnsprovider/rrstype:go_default_library",
"//federation/pkg/dnsprovider/tests:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//federation/pkg/dnsprovider/providers/coredns/stubs:all-srcs",
],
tags = ["automanaged"],
)

View file

@ -0,0 +1,95 @@
/*
Copyright 2016 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 coredns is the implementation of pkg/dnsprovider interface for CoreDNS
package coredns
import (
"fmt"
etcdc "github.com/coreos/etcd/client"
"github.com/golang/glog"
"gopkg.in/gcfg.v1"
"io"
"k8s.io/kubernetes/federation/pkg/dnsprovider"
"strconv"
"strings"
)
// "coredns" should be used to use this DNS provider
const (
ProviderName = "coredns"
)
// Config to override defaults
type Config struct {
Global struct {
EtcdEndpoints string `gcfg:"etcd-endpoints"`
DNSZones string `gcfg:"zones"`
CoreDNSEndpoints string `gcfg:"coredns-endpoints"`
}
}
func init() {
dnsprovider.RegisterDnsProvider(ProviderName, func(config io.Reader) (dnsprovider.Interface, error) {
return newCoreDNSProviderInterface(config)
})
}
// newCoreDnsProviderInterface creates a new instance of an CoreDNS DNS Interface.
func newCoreDNSProviderInterface(config io.Reader) (*Interface, error) {
etcdEndpoints := "http://federation-dns-server-etcd:2379"
etcdPathPrefix := "skydns"
dnsZones := ""
// Possibly override defaults with config below
if config != nil {
var cfg Config
if err := gcfg.ReadInto(&cfg, config); err != nil {
glog.Errorf("Couldn't read config: %v", err)
return nil, err
}
etcdEndpoints = cfg.Global.EtcdEndpoints
dnsZones = cfg.Global.DNSZones
}
glog.Infof("Using CoreDNS DNS provider")
if dnsZones == "" {
return nil, fmt.Errorf("Need to provide at least one DNS Zone")
}
etcdCfg := etcdc.Config{
Endpoints: strings.Split(etcdEndpoints, ","),
Transport: etcdc.DefaultTransport,
}
c, err := etcdc.New(etcdCfg)
if err != nil {
return nil, fmt.Errorf("Create etcd client from the config failed")
}
etcdKeysAPI := etcdc.NewKeysAPI(c)
intf := newInterfaceWithStub(etcdKeysAPI)
intf.etcdPathPrefix = etcdPathPrefix
zoneList := strings.Split(dnsZones, ",")
intf.zones = Zones{intf: intf}
for index, zoneName := range zoneList {
zone := Zone{domain: zoneName, id: strconv.Itoa(index), zones: &intf.zones}
intf.zones.zoneList = append(intf.zones.zoneList, zone)
}
return intf, nil
}

View file

@ -0,0 +1,270 @@
/*
Copyright 2016 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 coredns
import (
"flag"
"fmt"
"os"
"strconv"
"testing"
"k8s.io/kubernetes/federation/pkg/dnsprovider"
corednstesting "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/coredns/stubs"
"k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype"
"k8s.io/kubernetes/federation/pkg/dnsprovider/tests"
"strings"
)
func newTestInterface() (dnsprovider.Interface, error) {
// Use this to test the real cloud service.
// return dnsprovider.GetDnsProvider(ProviderName, strings.NewReader("\n[global]\nproject-id = federation0-cluster00"))
return newFakeInterface() // Use this to stub out the entire cloud service
}
func newFakeInterface() (dnsprovider.Interface, error) {
var service corednstesting.EtcdKeysAPI
service = corednstesting.NewEtcdKeysAPIStub()
intf := newInterfaceWithStub(service)
intf.etcdPathPrefix = "skydns"
zoneList := strings.Split("example.com,federation.io", ",")
intf.zones = Zones{intf: intf}
for index, zoneName := range zoneList {
zone := Zone{domain: zoneName, id: strconv.Itoa(index), zones: &intf.zones}
intf.zones.zoneList = append(intf.zones.zoneList, zone)
}
return intf, nil
}
var intf dnsprovider.Interface
func TestMain(m *testing.M) {
fmt.Printf("Parsing flags.\n")
flag.Parse()
var err error
fmt.Printf("Getting new test interface.\n")
intf, err = newTestInterface()
if err != nil {
fmt.Printf("Error creating interface: %v", err)
os.Exit(1)
}
fmt.Printf("Running tests...\n")
os.Exit(m.Run())
}
// zones returns the zones interface for the configured dns provider account/project,
// or fails if it can't be found
func zones(t *testing.T) dnsprovider.Zones {
zonesInterface, supported := intf.Zones()
if !supported {
t.Fatalf("Zones interface not supported by interface %v", intf)
} else {
t.Logf("Got zones %v\n", zonesInterface)
}
return zonesInterface
}
// firstZone returns the first zone for the configured dns provider account/project,
// or fails if it can't be found
func firstZone(t *testing.T) dnsprovider.Zone {
t.Logf("Getting zones")
z := zones(t)
zones, err := z.List()
if err != nil {
t.Fatalf("Failed to list zones: %v", err)
} else {
t.Logf("Got zone list: %v\n", zones)
}
if len(zones) < 1 {
t.Fatalf("Zone listing returned %d, expected >= %d", len(zones), 1)
} else {
t.Logf("Got at least 1 zone in list:%v\n", zones[0])
}
return zones[0]
}
/* rrs returns the ResourceRecordSets interface for a given zone */
func rrs(t *testing.T, zone dnsprovider.Zone) (r dnsprovider.ResourceRecordSets) {
rrsets, supported := zone.ResourceRecordSets()
if !supported {
t.Fatalf("ResourceRecordSets interface not supported by zone %v", zone)
return r
}
return rrsets
}
func listRrsOrFail(t *testing.T, rrsets dnsprovider.ResourceRecordSets) []dnsprovider.ResourceRecordSet {
rrset, err := rrsets.List()
if err != nil {
t.Fatalf("Failed to list recordsets: %v", err)
} else {
if len(rrset) < 0 {
t.Fatalf("Record set length=%d, expected >=0", len(rrset))
} else {
t.Logf("Got %d recordsets: %v", len(rrset), rrset)
}
}
return rrset
}
func getRrOrFail(t *testing.T, rrsets dnsprovider.ResourceRecordSets, name string) []dnsprovider.ResourceRecordSet {
rrsetList, err := rrsets.Get(name)
if err != nil {
t.Fatalf("Failed to get recordset: %v", err)
} else if len(rrsetList) == 0 {
t.Logf("Did not Get recordset: %v", name)
} else {
t.Logf("Got recordsets: %v", rrsetList)
}
return rrsetList
}
func getExampleRrs(zone dnsprovider.Zone) dnsprovider.ResourceRecordSet {
rrsets, _ := zone.ResourceRecordSets()
return rrsets.New("www11."+zone.Name(), []string{"10.10.10.10", "169.20.20.20"}, 180, rrstype.A)
}
func addRrsetOrFail(t *testing.T, rrsets dnsprovider.ResourceRecordSets, rrset dnsprovider.ResourceRecordSet) {
err := rrsets.StartChangeset().Add(rrset).Apply()
if err != nil {
t.Fatalf("Failed to add recordsets: %v", err)
}
}
/* TestZonesList verifies that listing of zones succeeds */
func TestZonesList(t *testing.T) {
firstZone(t)
}
/* TestZonesID verifies that the id of the zone is unique */
func TestZonesID(t *testing.T) {
zone := firstZone(t)
zoneID := zone.ID()
if zoneID != "0" {
t.Fatalf("Unexpected zone id: %q", zoneID)
}
}
/* TestResourceRecordSetsGet verifies that getting of RRS succeeds */
func TestResourceRecordSetsGet(t *testing.T) {
getRrOrFail(t, rrs(t, firstZone(t)), "example.com")
}
/* TestResourceRecordSetsAddSuccess verifies that addition of a valid RRS succeeds */
func TestResourceRecordSetsAddSuccess(t *testing.T) {
zone := firstZone(t)
sets := rrs(t, zone)
set := getExampleRrs(zone)
addRrsetOrFail(t, sets, set)
defer sets.StartChangeset().Remove(set).Apply()
t.Logf("Successfully added resource record set: %v", set)
if sets.Zone().ID() != zone.ID() {
t.Errorf("Zone for rrset does not match expected")
}
}
/* TestResourceRecordSetsAdditionVisible verifies that added RRS is visible after addition */
func TestResourceRecordSetsAdditionVisible(t *testing.T) {
zone := firstZone(t)
sets := rrs(t, zone)
rrset := getExampleRrs(zone)
addRrsetOrFail(t, sets, rrset)
defer sets.StartChangeset().Remove(rrset).Apply()
t.Logf("Successfully added resource record set: %v", rrset)
record := getRrOrFail(t, sets, rrset.Name())
if record == nil {
t.Errorf("Failed to find added resource record set %s", rrset.Name())
}
}
/* TestResourceRecordSetsAddDuplicateFail verifies that addition of a duplicate RRS fails */
func TestResourceRecordSetsAddDuplicateFail(t *testing.T) {
zone := firstZone(t)
sets := rrs(t, zone)
rrset := getExampleRrs(zone)
addRrsetOrFail(t, sets, rrset)
defer sets.StartChangeset().Remove(rrset).Apply()
t.Logf("Successfully added resource record set: %v", rrset)
// Try to add it again, and verify that the call fails.
err := sets.StartChangeset().Add(rrset).Apply()
if err == nil {
defer sets.StartChangeset().Remove(rrset).Apply()
t.Errorf("Should have failed to add duplicate resource record %v, but succeeded instead.", rrset)
} else {
t.Logf("Correctly failed to add duplicate resource record %v: %v", rrset, err)
}
}
/* TestResourceRecordSetsRemove verifies that the removal of an existing RRS succeeds */
func TestResourceRecordSetsRemove(t *testing.T) {
zone := firstZone(t)
sets := rrs(t, zone)
rrset := getExampleRrs(zone)
addRrsetOrFail(t, sets, rrset)
err := sets.StartChangeset().Remove(rrset).Apply()
if err != nil {
// Try again to clean up.
defer sets.StartChangeset().Remove(rrset).Apply()
t.Errorf("Failed to remove resource record set %v after adding", rrset)
} else {
t.Logf("Successfully removed resource set %v after adding", rrset)
}
}
/* TestResourceRecordSetsRemoveGone verifies that a removed RRS no longer exists */
func TestResourceRecordSetsRemoveGone(t *testing.T) {
zone := firstZone(t)
sets := rrs(t, zone)
rrset := getExampleRrs(zone)
addRrsetOrFail(t, sets, rrset)
err := sets.StartChangeset().Remove(rrset).Apply()
if err != nil {
// Try again to clean up.
defer sets.StartChangeset().Remove(rrset).Apply()
t.Errorf("Failed to remove resource record set %v after adding", rrset)
} else {
t.Logf("Successfully removed resource set %v after adding", rrset)
}
record := getRrOrFail(t, sets, rrset.Name())
if record != nil {
t.Errorf("Deleted resource record set %v is still present", rrset)
}
}
/* TestResourceRecordSetsReplace verifies that replacing an RRS works */
func TestResourceRecordSetsReplace(t *testing.T) {
zone := firstZone(t)
tests.CommonTestResourceRecordSetsReplace(t, zone)
}
/* TestResourceRecordSetsReplaceAll verifies that we can remove an RRS and create one with a different name */
func TestResourceRecordSetsReplaceAll(t *testing.T) {
zone := firstZone(t)
tests.CommonTestResourceRecordSetsReplaceAll(t, zone)
}
/* TestResourceRecordSetsDifferentTypes verifies that we can add records with same name, but different types */
func TestResourceRecordSetsDifferentTypes(t *testing.T) {
zone := firstZone(t)
tests.CommonTestResourceRecordSetsDifferentTypes(t, zone)
}

View file

@ -0,0 +1,41 @@
/*
Copyright 2016 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 coredns
import (
"k8s.io/kubernetes/federation/pkg/dnsprovider"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/coredns/stubs"
)
// Compile time check for interface adherence
var _ dnsprovider.Interface = Interface{}
type Interface struct {
etcdKeysAPI stubs.EtcdKeysAPI
etcdPathPrefix string
zones Zones
}
// newInterfaceWithStub facilitates stubbing out the underlying etcd
// library for testing purposes. It returns an provider-independent interface.
func newInterfaceWithStub(etcdKeysAPI stubs.EtcdKeysAPI) *Interface {
return &Interface{etcdKeysAPI: etcdKeysAPI}
}
func (i Interface) Zones() (dnsprovider.Zones, bool) {
return i.zones, true
}

View file

@ -0,0 +1,148 @@
/*
Copyright 2016 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 coredns
import (
"encoding/json"
"fmt"
"hash/fnv"
etcdc "github.com/coreos/etcd/client"
dnsmsg "github.com/miekg/coredns/middleware/etcd/msg"
"golang.org/x/net/context"
"k8s.io/kubernetes/federation/pkg/dnsprovider"
)
// Compile time check for interface adherence
var _ dnsprovider.ResourceRecordChangeset = &ResourceRecordChangeset{}
type ChangeSetType string
const (
ADDITION = ChangeSetType("ADDITION")
DELETION = ChangeSetType("DELETION")
UPSERT = ChangeSetType("UPSERT")
)
type ChangeSet struct {
cstype ChangeSetType
rrset dnsprovider.ResourceRecordSet
}
type ResourceRecordChangeset struct {
zone *Zone
rrsets *ResourceRecordSets
changeset []ChangeSet
}
func (c *ResourceRecordChangeset) Add(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset {
c.changeset = append(c.changeset, ChangeSet{cstype: ADDITION, rrset: rrset})
return c
}
func (c *ResourceRecordChangeset) Remove(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset {
c.changeset = append(c.changeset, ChangeSet{cstype: DELETION, rrset: rrset})
return c
}
func (c *ResourceRecordChangeset) IsEmpty() bool {
return len(c.changeset) == 0
}
func (c *ResourceRecordChangeset) Upsert(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset {
c.changeset = append(c.changeset, ChangeSet{cstype: UPSERT, rrset: rrset})
return c
}
func (c *ResourceRecordChangeset) Apply() error {
ctx := context.Background()
etcdPathPrefix := c.zone.zones.intf.etcdPathPrefix
getOpts := &etcdc.GetOptions{}
setOpts := &etcdc.SetOptions{}
deleteOpts := &etcdc.DeleteOptions{
Recursive: true,
}
for _, changeset := range c.changeset {
switch changeset.cstype {
case ADDITION, UPSERT:
checkNotExists := changeset.cstype == ADDITION
// TODO: I think the semantics of the other providers are different; they operate at the record level, not the individual rrdata level
// In other words: we should insert/replace all the records for the key
for _, rrdata := range changeset.rrset.Rrdatas() {
b, err := json.Marshal(&dnsmsg.Service{Host: rrdata, TTL: uint32(changeset.rrset.Ttl()), Group: changeset.rrset.Name()})
if err != nil {
return err
}
recordValue := string(b)
recordLabel := getHash(rrdata)
recordKey := buildDNSNameString(changeset.rrset.Name(), recordLabel)
if checkNotExists {
response, err := c.zone.zones.intf.etcdKeysAPI.Get(ctx, dnsmsg.Path(recordKey, etcdPathPrefix), getOpts)
if err == nil && response != nil {
return fmt.Errorf("Key already exist, key: %v", recordKey)
}
}
_, err = c.zone.zones.intf.etcdKeysAPI.Set(ctx, dnsmsg.Path(recordKey, etcdPathPrefix), recordValue, setOpts)
if err != nil {
return err
}
}
case DELETION:
// TODO: I think the semantics of the other providers are different; they operate at the record level, not the individual rrdata level
// In other words: we should delete all the records for the key, only if it matches exactly
for _, rrdata := range changeset.rrset.Rrdatas() {
recordLabel := getHash(rrdata)
recordKey := buildDNSNameString(changeset.rrset.Name(), recordLabel)
_, err := c.zone.zones.intf.etcdKeysAPI.Delete(ctx, dnsmsg.Path(recordKey, etcdPathPrefix), deleteOpts)
if err != nil {
return err
}
}
// TODO: We need to cleanup empty dirs in etcd
}
}
return nil
}
// ResourceRecordSets returns the parent ResourceRecordSets
func (c *ResourceRecordChangeset) ResourceRecordSets() dnsprovider.ResourceRecordSets {
return c.rrsets
}
func getHash(text string) string {
h := fnv.New32a()
h.Write([]byte(text))
return fmt.Sprintf("%x", h.Sum32())
}
func buildDNSNameString(labels ...string) string {
var res string
for _, label := range labels {
if res == "" {
res = label
} else {
res = fmt.Sprintf("%s.%s", label, res)
}
}
return res
}

View file

@ -0,0 +1,49 @@
/*
Copyright 2016 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 coredns
import (
"k8s.io/kubernetes/federation/pkg/dnsprovider"
"k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype"
)
// Compile time check for interface adherence
var _ dnsprovider.ResourceRecordSet = ResourceRecordSet{}
type ResourceRecordSet struct {
name string
rrdatas []string
ttl int64
rrsType rrstype.RrsType
rrsets *ResourceRecordSets
}
func (rrset ResourceRecordSet) Name() string {
return rrset.name
}
func (rrset ResourceRecordSet) Rrdatas() []string {
return rrset.rrdatas
}
func (rrset ResourceRecordSet) Ttl() int64 {
return rrset.ttl
}
func (rrset ResourceRecordSet) Type() rrstype.RrsType {
return rrset.rrsType
}

View file

@ -0,0 +1,114 @@
/*
Copyright 2016 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 coredns
import (
"encoding/json"
"fmt"
etcdc "github.com/coreos/etcd/client"
"github.com/golang/glog"
dnsmsg "github.com/miekg/coredns/middleware/etcd/msg"
"golang.org/x/net/context"
"k8s.io/kubernetes/federation/pkg/dnsprovider"
"k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype"
"net"
)
// Compile time check for interface adherence
var _ dnsprovider.ResourceRecordSets = ResourceRecordSets{}
type ResourceRecordSets struct {
zone *Zone
}
func (rrsets ResourceRecordSets) List() ([]dnsprovider.ResourceRecordSet, error) {
var list []dnsprovider.ResourceRecordSet
return list, fmt.Errorf("OperationNotSupported")
}
func (rrsets ResourceRecordSets) Get(name string) ([]dnsprovider.ResourceRecordSet, error) {
getOpts := &etcdc.GetOptions{
Recursive: true,
}
etcdPathPrefix := rrsets.zone.zones.intf.etcdPathPrefix
response, err := rrsets.zone.zones.intf.etcdKeysAPI.Get(context.Background(), dnsmsg.Path(name, etcdPathPrefix), getOpts)
if err != nil {
if etcdc.IsKeyNotFound(err) {
glog.V(2).Infof("Subdomain %q does not exist", name)
return nil, nil
}
return nil, fmt.Errorf("Failed to get service from etcd, err: %v", err)
}
if emptyResponse(response) {
glog.V(2).Infof("Subdomain %q does not exist in etcd", name)
return nil, nil
}
var list []dnsprovider.ResourceRecordSet
for _, node := range response.Node.Nodes {
service := dnsmsg.Service{}
err = json.Unmarshal([]byte(node.Value), &service)
if err != nil {
return nil, fmt.Errorf("Failed to unmarshall json data, err: %v", err)
}
rrset := ResourceRecordSet{name: name, rrdatas: []string{}, rrsets: &rrsets}
ip := net.ParseIP(service.Host)
switch {
case ip == nil:
rrset.rrsType = rrstype.CNAME
case ip.To4() != nil:
rrset.rrsType = rrstype.A
case ip.To16() != nil:
rrset.rrsType = rrstype.AAAA
default:
// Cannot occur
}
rrset.rrdatas = append(rrset.rrdatas, service.Host)
rrset.ttl = int64(service.TTL)
list = append(list, rrset)
}
return list, nil
}
func (rrsets ResourceRecordSets) StartChangeset() dnsprovider.ResourceRecordChangeset {
return &ResourceRecordChangeset{
zone: rrsets.zone,
rrsets: &rrsets,
}
}
func (rrsets ResourceRecordSets) New(name string, rrdatas []string, ttl int64, rrsType rrstype.RrsType) dnsprovider.ResourceRecordSet {
return ResourceRecordSet{
name: name,
rrdatas: rrdatas,
ttl: ttl,
rrsType: rrsType,
rrsets: &rrsets,
}
}
// Zone returns the parent zone
func (rrset ResourceRecordSets) Zone() dnsprovider.Zone {
return rrset.zone
}
func emptyResponse(resp *etcdc.Response) bool {
return resp == nil || resp.Node == nil || (len(resp.Node.Value) == 0 && len(resp.Node.Nodes) == 0)
}

View file

@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["corednsapi.go"],
deps = [
"//vendor/github.com/coreos/etcd/client:go_default_library",
"//vendor/golang.org/x/net/context:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,85 @@
/*
Copyright 2016 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 stubs implements a stub for the EtcdKeysAPI, used primarily for unit testing purposes
package stubs
import (
"strings"
etcd "github.com/coreos/etcd/client"
"golang.org/x/net/context"
)
// Compile time check for interface conformance
var _ EtcdKeysAPI = &EtcdKeysAPIStub{}
type EtcdKeysAPI interface {
Set(context context.Context, key, value string, options *etcd.SetOptions) (*etcd.Response, error)
Get(context context.Context, key string, options *etcd.GetOptions) (*etcd.Response, error)
Delete(context context.Context, key string, options *etcd.DeleteOptions) (*etcd.Response, error)
}
type EtcdKeysAPIStub struct {
writes map[string]string
}
// NewEtcdKeysAPIStub returns an initialized EtcdKeysAPIStub
func NewEtcdKeysAPIStub() *EtcdKeysAPIStub {
return &EtcdKeysAPIStub{make(map[string]string)}
}
func (ec *EtcdKeysAPIStub) Set(context context.Context, key, value string, options *etcd.SetOptions) (*etcd.Response, error) {
ec.writes[key] = value
return nil, nil
}
func (ec *EtcdKeysAPIStub) Delete(context context.Context, key string, options *etcd.DeleteOptions) (*etcd.Response, error) {
for p := range ec.writes {
if (options.Recursive && strings.HasPrefix(p, key)) || (!options.Recursive && p == key) {
delete(ec.writes, p)
}
}
return nil, nil
}
func (ec *EtcdKeysAPIStub) Get(context context.Context, key string, options *etcd.GetOptions) (*etcd.Response, error) {
nodes := ec.GetAll(key)
if len(nodes) == 0 {
return nil, nil
}
if len(nodes) == 1 && nodes[key] != "" {
return &etcd.Response{Node: &etcd.Node{Key: key, Value: nodes[key], Dir: false}}, nil
}
node := &etcd.Node{Key: key, Dir: true, Nodes: etcd.Nodes{}}
for k, v := range nodes {
n := &etcd.Node{Key: k, Value: v}
node.Nodes = append(node.Nodes, n)
}
return &etcd.Response{Node: node}, nil
}
func (ec *EtcdKeysAPIStub) GetAll(key string) map[string]string {
nodes := make(map[string]string)
key = strings.ToLower(key)
for path := range ec.writes {
if strings.HasPrefix(path, key) {
nodes[path] = ec.writes[path]
}
}
return nodes
}

View file

@ -0,0 +1,42 @@
/*
Copyright 2016 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 coredns
import (
"k8s.io/kubernetes/federation/pkg/dnsprovider"
)
// Compile time check for interface adherence
var _ dnsprovider.Zone = Zone{}
type Zone struct {
domain string
id string
zones *Zones
}
func (zone Zone) Name() string {
return zone.domain
}
func (zone Zone) ID() string {
return zone.id
}
func (zone Zone) ResourceRecordSets() (dnsprovider.ResourceRecordSets, bool) {
return &ResourceRecordSets{zone: &zone}, true
}

View file

@ -0,0 +1,49 @@
/*
Copyright 2016 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 coredns
import (
"fmt"
"k8s.io/kubernetes/federation/pkg/dnsprovider"
)
// Compile time check for interface adherence
var _ dnsprovider.Zones = Zones{}
type Zones struct {
intf *Interface
zoneList []Zone
}
func (zones Zones) List() ([]dnsprovider.Zone, error) {
var zoneList []dnsprovider.Zone
for _, zone := range zones.zoneList {
zoneList = append(zoneList, zone)
}
return zoneList, nil
}
func (zones Zones) Add(zone dnsprovider.Zone) (dnsprovider.Zone, error) {
return &Zone{}, fmt.Errorf("OperationNotSupported")
}
func (zones Zones) Remove(zone dnsprovider.Zone) error {
return fmt.Errorf("OperationNotSupported")
}
func (zones Zones) New(name string) (dnsprovider.Zone, error) {
return &Zone{}, fmt.Errorf("OperationNotSupported")
}

View file

@ -0,0 +1,62 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"clouddns.go",
"interface.go",
"rrchangeset.go",
"rrset.go",
"rrsets.go",
"zone.go",
"zones.go",
],
deps = [
"//federation/pkg/dnsprovider:go_default_library",
"//federation/pkg/dnsprovider/providers/google/clouddns/internal:go_default_library",
"//federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces:go_default_library",
"//federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs:go_default_library",
"//federation/pkg/dnsprovider/rrstype:go_default_library",
"//pkg/cloudprovider/providers/gce:go_default_library",
"//vendor/cloud.google.com/go/compute/metadata:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/golang.org/x/oauth2:go_default_library",
"//vendor/golang.org/x/oauth2/google:go_default_library",
"//vendor/google.golang.org/api/compute/v1:go_default_library",
"//vendor/google.golang.org/api/dns/v1:go_default_library",
"//vendor/gopkg.in/gcfg.v1:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["clouddns_test.go"],
library = ":go_default_library",
deps = [
"//federation/pkg/dnsprovider:go_default_library",
"//federation/pkg/dnsprovider/rrstype:go_default_library",
"//federation/pkg/dnsprovider/tests:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//federation/pkg/dnsprovider/providers/google/clouddns/internal:all-srcs",
],
tags = ["automanaged"],
)

View file

@ -0,0 +1,116 @@
/*
Copyright 2016 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.
*/
// clouddns is the implementation of pkg/dnsprovider interface for Google Cloud DNS
package clouddns
import (
"io"
"cloud.google.com/go/compute/metadata"
"github.com/golang/glog"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
compute "google.golang.org/api/compute/v1"
dns "google.golang.org/api/dns/v1"
gcfg "gopkg.in/gcfg.v1"
"k8s.io/kubernetes/federation/pkg/dnsprovider"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs"
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
)
const (
ProviderName = "google-clouddns"
)
func init() {
dnsprovider.RegisterDnsProvider(ProviderName, func(config io.Reader) (dnsprovider.Interface, error) {
return newCloudDns(config)
})
}
type Config struct {
Global struct {
TokenURL string `gcfg:"token-url"`
TokenBody string `gcfg:"token-body"`
ProjectID string `gcfg:"project-id"`
}
}
// newCloudDns creates a new instance of a Google Cloud DNS Interface.
func newCloudDns(config io.Reader) (*Interface, error) {
projectID, _ := metadata.ProjectID() // On error we get an empty string, which is fine for now.
var tokenSource oauth2.TokenSource
// Possibly override defaults with config below
if config != nil {
var cfg Config
if err := gcfg.ReadInto(&cfg, config); err != nil {
glog.Errorf("Couldn't read config: %v", err)
return nil, err
}
glog.Infof("Using Google Cloud DNS provider config %+v", cfg)
if cfg.Global.ProjectID != "" {
projectID = cfg.Global.ProjectID
}
if cfg.Global.TokenURL != "" {
tokenSource = gce.NewAltTokenSource(cfg.Global.TokenURL, cfg.Global.TokenBody)
}
}
return CreateInterface(projectID, tokenSource)
}
// CreateInterface creates a clouddns.Interface object using the specified parameters.
// If no tokenSource is specified, uses oauth2.DefaultTokenSource.
func CreateInterface(projectID string, tokenSource oauth2.TokenSource) (*Interface, error) {
if tokenSource == nil {
var err error
tokenSource, err = google.DefaultTokenSource(
oauth2.NoContext,
compute.CloudPlatformScope,
compute.ComputeScope)
glog.Infof("Using DefaultTokenSource %#v", tokenSource)
if err != nil {
return nil, err
}
} else {
glog.Infof("Using existing Token Source %#v", tokenSource)
}
oauthClient := oauth2.NewClient(oauth2.NoContext, tokenSource)
service, err := dns.New(oauthClient)
if err != nil {
glog.Errorf("Failed to get Cloud DNS client: %v", err)
}
glog.Infof("Successfully got DNS service: %v\n", service)
return newInterfaceWithStub(projectID, internal.NewService(service)), nil
}
// NewFakeInterface returns a fake clouddns interface, useful for unit testing purposes.
func NewFakeInterface() (dnsprovider.Interface, error) {
service := stubs.NewService()
interface_ := newInterfaceWithStub("", service)
zones := service.ManagedZones_
// Add a fake zone to test against.
zone := &stubs.ManagedZone{Service: zones, Name_: "example.com", Rrsets: []stubs.ResourceRecordSet{}, Id_: 1}
call := zones.Create(interface_.project(), zone)
if _, err := call.Do(); err != nil {
return nil, err
}
return interface_, nil
}

View file

@ -0,0 +1,273 @@
/*
Copyright 2016 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 clouddns
import (
"flag"
"fmt"
"os"
"testing"
"k8s.io/kubernetes/federation/pkg/dnsprovider"
"k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype"
"k8s.io/kubernetes/federation/pkg/dnsprovider/tests"
)
func newTestInterface() (dnsprovider.Interface, error) {
// Use this to test the real cloud service - insert appropriate project-id. Default token source will be used. See
// https://github.com/golang/oauth2/blob/master/google/default.go for details.
// return dnsprovider.GetDnsProvider(ProviderName, strings.NewReader("\n[global]\nproject-id = federation0-cluster00"))
return NewFakeInterface() // Use this to stub out the entire cloud service
}
var interface_ dnsprovider.Interface
func TestMain(m *testing.M) {
flag.Parse()
var err error
interface_, err = newTestInterface()
if err != nil {
fmt.Printf("Error creating interface: %v", err)
os.Exit(1)
}
os.Exit(m.Run())
}
// zones returns the zones interface for the configured dns provider account/project,
// or fails if it can't be found
func zones(t *testing.T) dnsprovider.Zones {
zonesInterface, supported := interface_.Zones()
if !supported {
t.Fatalf("Zones interface not supported by interface %v", interface_)
} else {
t.Logf("Got zones %v\n", zonesInterface)
}
return zonesInterface
}
// firstZone returns the first zone for the configured dns provider account/project,
// or fails if it can't be found
func firstZone(t *testing.T) dnsprovider.Zone {
t.Logf("Getting zones")
zones, err := zones(t).List()
if err != nil {
t.Fatalf("Failed to list zones: %v", err)
} else {
t.Logf("Got zone list: %v\n", zones)
}
if len(zones) < 1 {
t.Fatalf("Zone listing returned %d, expected >= %d", len(zones), 1)
} else {
t.Logf("Got at least 1 zone in list:%v\n", zones[0])
}
return zones[0]
}
/* rrs returns the ResourceRecordSets interface for a given zone */
func rrs(t *testing.T, zone dnsprovider.Zone) (r dnsprovider.ResourceRecordSets) {
rrsets, supported := zone.ResourceRecordSets()
if !supported {
t.Fatalf("ResourceRecordSets interface not supported by zone %v", zone)
return r
}
return rrsets
}
func listRrsOrFail(t *testing.T, rrsets dnsprovider.ResourceRecordSets) []dnsprovider.ResourceRecordSet {
rrset, err := rrsets.List()
if err != nil {
t.Fatalf("Failed to list recordsets: %v", err)
} else {
if len(rrset) < 0 {
t.Fatalf("Record set length=%d, expected >=0", len(rrset))
} else {
t.Logf("Got %d recordsets: %v", len(rrset), rrset)
}
}
return rrset
}
func getExampleRrs(zone dnsprovider.Zone) dnsprovider.ResourceRecordSet {
rrsets, _ := zone.ResourceRecordSets()
return rrsets.New("www11."+zone.Name(), []string{"10.10.10.10", "169.20.20.20"}, 180, rrstype.A)
}
func getInvalidRrs(zone dnsprovider.Zone) dnsprovider.ResourceRecordSet {
rrsets, _ := zone.ResourceRecordSets()
return rrsets.New("www12."+zone.Name(), []string{"rubbish", "rubbish"}, 180, rrstype.A)
}
func addRrsetOrFail(t *testing.T, rrsets dnsprovider.ResourceRecordSets, rrset dnsprovider.ResourceRecordSet) {
err := rrsets.StartChangeset().Add(rrset).Apply()
if err != nil {
t.Fatalf("Failed to add recordsets: %v", err)
}
}
/* TestZonesList verifies that listing of zones succeeds */
func TestZonesList(t *testing.T) {
firstZone(t)
}
/* TestZonesID verifies that the id of the zone is returned with the prefix removed */
func TestZonesID(t *testing.T) {
zone := firstZone(t)
zoneID := zone.ID()
if zoneID != "1" {
t.Fatalf("Unexpected zone id: %q", zoneID)
}
}
/* TestZoneAddSuccess verifies that addition of a valid managed DNS zone succeeds */
func TestZoneAddSuccess(t *testing.T) {
testZoneName := "ubernetesv2.test."
t.Logf("Getting zones")
z := zones(t)
t.Logf("Got zones, making new Zone")
input, err := z.New(testZoneName)
if err != nil {
t.Errorf("Failed to allocate new zone object %s: %v", testZoneName, err)
}
zone, err := z.Add(input)
if err != nil {
t.Errorf("Failed to create new managed DNS zone %s: %v", testZoneName, err)
}
defer func(zone dnsprovider.Zone) {
if zone != nil {
if err := z.Remove(zone); err != nil {
t.Errorf("Failed to delete zone %v: %v", zone, err)
}
}
}(zone)
t.Logf("Successfully added managed DNS zone: %v", zone)
}
/* TestResourceRecordSetsList verifies that listing of RRS's succeeds */
func TestResourceRecordSetsList(t *testing.T) {
listRrsOrFail(t, rrs(t, firstZone(t)))
}
/* TestResourceRecordSetsAddSuccess verifies that addition of a valid RRS succeeds */
func TestResourceRecordSetsAddSuccess(t *testing.T) {
zone := firstZone(t)
sets := rrs(t, zone)
set := getExampleRrs(zone)
addRrsetOrFail(t, sets, set)
defer sets.StartChangeset().Remove(set).Apply()
t.Logf("Successfully added resource record set: %v", set)
}
/* TestResourceRecordSetsAdditionVisible verifies that added RRS is visible after addition */
func TestResourceRecordSetsAdditionVisible(t *testing.T) {
zone := firstZone(t)
sets := rrs(t, zone)
rrset := getExampleRrs(zone)
addRrsetOrFail(t, sets, rrset)
defer sets.StartChangeset().Remove(rrset).Apply()
t.Logf("Successfully added resource record set: %v", rrset)
found := false
for _, record := range listRrsOrFail(t, sets) {
if record.Name() == rrset.Name() {
found = true
break
}
}
if !found {
t.Errorf("Failed to find added resource record set %s", rrset.Name())
}
}
/* TestResourceRecordSetsAddDuplicateFail verifies that addition of a duplicate RRS fails */
func TestResourceRecordSetsAddDuplicateFail(t *testing.T) {
zone := firstZone(t)
sets := rrs(t, zone)
rrset := getExampleRrs(zone)
addRrsetOrFail(t, sets, rrset)
defer sets.StartChangeset().Remove(rrset).Apply()
t.Logf("Successfully added resource record set: %v", rrset)
// Try to add it again, and verify that the call fails.
err := sets.StartChangeset().Add(rrset).Apply()
if err == nil {
defer sets.StartChangeset().Remove(rrset).Apply()
t.Errorf("Should have failed to add duplicate resource record %v, but succeeded instead.", rrset)
} else {
t.Logf("Correctly failed to add duplicate resource record %v: %v", rrset, err)
}
}
/* TestResourceRecordSetsRemove verifies that the removal of an existing RRS succeeds */
func TestResourceRecordSetsRemove(t *testing.T) {
zone := firstZone(t)
sets := rrs(t, zone)
rrset := getExampleRrs(zone)
addRrsetOrFail(t, sets, rrset)
err := sets.StartChangeset().Remove(rrset).Apply()
if err != nil {
// Try again to clean up.
defer sets.StartChangeset().Remove(rrset).Apply()
t.Errorf("Failed to remove resource record set %v after adding: %v", rrset, err)
} else {
t.Logf("Successfully removed resource set %v after adding", rrset)
}
}
/* TestResourceRecordSetsRemoveGone verifies that a removed RRS no longer exists */
func TestResourceRecordSetsRemoveGone(t *testing.T) {
zone := firstZone(t)
sets := rrs(t, zone)
rrset := getExampleRrs(zone)
addRrsetOrFail(t, sets, rrset)
err := sets.StartChangeset().Remove(rrset).Apply()
if err != nil {
// Try again to clean up.
defer sets.StartChangeset().Remove(rrset).Apply()
t.Errorf("Failed to remove resource record set %v after adding: %v", rrset, err)
} else {
t.Logf("Successfully removed resource set %v after adding", rrset)
}
// Check that it's gone
list := listRrsOrFail(t, sets)
found := false
for _, set := range list {
if set.Name() == rrset.Name() {
found = true
break
}
}
if found {
t.Errorf("Deleted resource record set %v is still present", rrset)
}
}
/* TestResourceRecordSetsReplace verifies that replacing an RRS works */
func TestResourceRecordSetsReplace(t *testing.T) {
zone := firstZone(t)
tests.CommonTestResourceRecordSetsReplace(t, zone)
}
/* TestResourceRecordSetsReplaceAll verifies that we can remove an RRS and create one with a different name*/
func TestResourceRecordSetsReplaceAll(t *testing.T) {
zone := firstZone(t)
tests.CommonTestResourceRecordSetsReplaceAll(t, zone)
}
/* TestResourceRecordSetsDifferentType verifies that we can add records of the same name but different types */
func TestResourceRecordSetsDifferentTypes(t *testing.T) {
zone := firstZone(t)
tests.CommonTestResourceRecordSetsDifferentTypes(t, zone)
}

View file

@ -0,0 +1,43 @@
/*
Copyright 2016 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 clouddns
import (
"k8s.io/kubernetes/federation/pkg/dnsprovider"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
var _ dnsprovider.Interface = Interface{}
type Interface struct {
project_ string
service interfaces.Service
}
// newInterfaceWithStub facilitates stubbing out the underlying Google Cloud DNS
// library for testing purposes. It returns an provider-independent interface.
func newInterfaceWithStub(project string, service interfaces.Service) *Interface {
return &Interface{project, service}
}
func (i Interface) Zones() (zones dnsprovider.Zones, supported bool) {
return Zones{i.service.ManagedZones(), &i}, true
}
func (i Interface) project() string {
return i.project_
}

View file

@ -0,0 +1,52 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"change.go",
"changes_create_call.go",
"changes_service.go",
"clouddns.go",
"managed_zone.go",
"managed_zone_create_call.go",
"managed_zones_delete_call.go",
"managed_zones_get_call.go",
"managed_zones_list_call.go",
"managed_zones_list_response.go",
"managed_zones_service.go",
"rrset.go",
"rrsets_list_call.go",
"rrsets_list_response.go",
"rrsets_service.go",
"service.go",
],
deps = [
"//federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces:go_default_library",
"//federation/pkg/dnsprovider/rrstype:go_default_library",
"//vendor/google.golang.org/api/dns/v1:go_default_library",
"//vendor/google.golang.org/api/googleapi:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces:all-srcs",
"//federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs:all-srcs",
],
tags = ["automanaged"],
)

View file

@ -0,0 +1,43 @@
/*
Copyright 2016 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 internal
import (
dns "google.golang.org/api/dns/v1"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ interfaces.Change = Change{}
type Change struct{ impl *dns.Change }
func (c Change) Additions() (rrsets []interfaces.ResourceRecordSet) {
rrsets = make([]interfaces.ResourceRecordSet, len(c.impl.Additions))
for index, addition := range c.impl.Additions {
rrsets[index] = interfaces.ResourceRecordSet(&ResourceRecordSet{addition})
}
return rrsets
}
func (c Change) Deletions() (rrsets []interfaces.ResourceRecordSet) {
rrsets = make([]interfaces.ResourceRecordSet, len(c.impl.Deletions))
for index, deletion := range c.impl.Deletions {
rrsets[index] = interfaces.ResourceRecordSet(&ResourceRecordSet{deletion})
}
return rrsets
}

View file

@ -0,0 +1,34 @@
/*
Copyright 2016 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 internal
import (
dns "google.golang.org/api/dns/v1"
"google.golang.org/api/googleapi"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ interfaces.ChangesCreateCall = ChangesCreateCall{}
type ChangesCreateCall struct{ impl *dns.ChangesCreateCall }
func (c ChangesCreateCall) Do(opts ...googleapi.CallOption) (interfaces.Change, error) {
ch, err := c.impl.Do(opts...)
return &Change{ch}, err
}

View file

@ -0,0 +1,43 @@
/*
Copyright 2016 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 internal
import (
dns "google.golang.org/api/dns/v1"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ interfaces.ChangesService = ChangesService{}
type ChangesService struct{ impl *dns.ChangesService }
func (c ChangesService) Create(project string, managedZone string, change interfaces.Change) interfaces.ChangesCreateCall {
return &ChangesCreateCall{c.impl.Create(project, managedZone, change.(*Change).impl)}
}
func (c ChangesService) NewChange(additions, deletions []interfaces.ResourceRecordSet) interfaces.Change {
adds := make([]*dns.ResourceRecordSet, len(additions))
deletes := make([]*dns.ResourceRecordSet, len(deletions))
for i, a := range additions {
adds[i] = a.(*ResourceRecordSet).impl
}
for i, d := range deletions {
deletes[i] = d.(*ResourceRecordSet).impl
}
return &Change{&dns.Change{Additions: adds, Deletions: deletes}}
}

View file

@ -0,0 +1,35 @@
/*
Copyright 2016 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 internal
// Implementation of internal/interfaces/* on top of Google Cloud DNS API.
// See https://godoc.org/google.golang.org/api/dns/v1 for details
// This facilitates stubbing out Google Cloud DNS for unit testing.
// Only the parts of the API that we use are included.
// Others can be added as needed.
import dns "google.golang.org/api/dns/v1"
type (
Project struct{ impl *dns.Project }
ProjectsGetCall struct{ impl *dns.ProjectsGetCall }
ProjectsService struct{ impl *dns.ProjectsService }
Quota struct{ impl *dns.Quota }
)

View file

@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["interfaces.go"],
deps = [
"//federation/pkg/dnsprovider/rrstype:go_default_library",
"//vendor/google.golang.org/api/googleapi:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,212 @@
/*
Copyright 2016 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 interfaces
import (
"context"
"google.golang.org/api/googleapi"
"k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype"
)
// Interfaces to directly mirror the Google Cloud DNS API structures.
// See https://godoc.org/google.golang.org/api/dns/v1 for details
// This facilitates stubbing out Google Cloud DNS for unit testing.
// Only the parts of the API that we use are included.
// Others can be added as needed.
type (
Change interface {
Additions() []ResourceRecordSet
Deletions() []ResourceRecordSet
// Id() string // TODO: Add as needed
// Kind() string // TODO: Add as needed
// StartTime() string // TODO: Add as needed
// Status() string // TODO: Add as needed
}
ChangesCreateCall interface {
// Context(ctx context.Context) *ChangesCreateCall // TODO: Add as needed
Do(opts ...googleapi.CallOption) (Change, error)
// Fields(s ...googleapi.Field) *ChangesCreateCall // TODO: Add as needed
}
ChangesGetCall interface {
// Context(ctx context.Context) *ChangesGetCall // TODO: Add as needed
Do(opts ...googleapi.CallOption) (*Change, error)
// Fields(s ...googleapi.Field) *ChangesGetCall // TODO: Add as needed
// IfNoneMatch(entityTag string) *ChangesGetCall // TODO: Add as needed
}
ChangesListCall interface {
// Context(ctx context.Context) *ChangesListCall // TODO: Add as needed
Do(opts ...googleapi.CallOption) (*ChangesListResponse, error)
// Fields(s ...googleapi.Field) *ChangesListCall // TODO: Add as needed
// IfNoneMatch(entityTag string) *ChangesListCall // TODO: Add as needed
// MaxResults(maxResults int64) *ChangesListCall // TODO: Add as needed
// PageToken(pageToken string) *ChangesListCall // TODO: Add as needed
// Pages(ctx context.Context, f func(*ChangesListResponse) error) error // TODO: Add as needed
// SortBy(sortBy string) *ChangesListCall // TODO: Add as needed
// SortOrder(sortOrder string) *ChangesListCall // TODO: Add as needed
}
ChangesListResponse interface {
// Changes() []*Change // TODO: Add as needed
// Kind() string // TODO: Add as needed
// NextPageToken() string // TODO: Add as needed
// ServerResponse() googleapi.ServerResponse // TODO: Add as needed
// ForceSendFields() []string // TODO: Add as needed
}
ChangesService interface {
// Create(project string, managedZone string, change *Change) *ChangesCreateCall // TODO: Add as needed
Create(project string, managedZone string, change Change) ChangesCreateCall
NewChange(additions, deletions []ResourceRecordSet) Change
// Get(project string, managedZone string, changeId string) *ChangesGetCall // TODO: Add as needed
// List(project string, managedZone string) *ChangesListCall // TODO: Add as needed
}
ManagedZone interface {
// CreationTime() string // TODO: Add as needed
// Description() string // TODO: Add as needed
DnsName() string
Id() uint64
// Kind() string // TODO: Add as needed
Name() string
// NameServerSet() string // TODO: Add as needed
// NameServers() []string // TODO: Add as needed
// ServerResponse() googleapi.ServerResponse // TODO: Add as needed
// ForceSendFields() []string // TODO: Add as needed
}
ManagedZonesCreateCall interface {
// Context(ctx context.Context) *ManagedZonesCreateCall // TODO: Add as needed
Do(opts ...googleapi.CallOption) (ManagedZone, error)
// Fields(s ...googleapi.Field) *ManagedZonesCreateCall // TODO: Add as needed
}
ManagedZonesDeleteCall interface {
// Context(ctx context.Context) *ManagedZonesDeleteCall // TODO: Add as needed
Do(opts ...googleapi.CallOption) error
// Fields(s ...googleapi.Field) *ManagedZonesDeleteCall // TODO: Add as needed
}
ManagedZonesGetCall interface {
// Context(ctx context.Context) *ManagedZonesGetCall // TODO: Add as needed
Do(opts ...googleapi.CallOption) (ManagedZone, error)
// Fields(s ...googleapi.Field) *ManagedZonesGetCall // TODO: Add as needed
// IfNoneMatch(entityTag string) *ManagedZonesGetCall // TODO: Add as needed
}
ManagedZonesListCall interface {
// Context(ctx context.Context) *ManagedZonesListCall // TODO: Add as needed
DnsName(dnsName string) ManagedZonesListCall
Do(opts ...googleapi.CallOption) (ManagedZonesListResponse, error)
// Fields(s ...googleapi.Field) *ManagedZonesListCall // TODO: Add as needed
// IfNoneMatch(entityTag string) *ManagedZonesListCall // TODO: Add as needed
// MaxResults(maxResults int64) *ManagedZonesListCall // TODO: Add as needed
// PageToken(pageToken string) *ManagedZonesListCall // TODO: Add as needed
// Pages(ctx context.Context, f func(*ManagedZonesListResponse) error) error // TODO: Add as needed
}
ManagedZonesListResponse interface {
// Kind() string // TODO: Add as needed
// ManagedZones() []*ManagedZone // TODO: Add as needed
ManagedZones() []ManagedZone
// NextPageToken string // TODO: Add as needed
// ServerResponse() googleapi.ServerResponse // TODO: Add as needed
// ForceSendFields() []string // TODO: Add as needed
}
ManagedZonesService interface {
// NewManagedZonesService(s *Service) *ManagedZonesService // TODO: Add to service if needed
Create(project string, managedZone ManagedZone) ManagedZonesCreateCall
Delete(project string, managedZone string) ManagedZonesDeleteCall
Get(project string, managedZone string) ManagedZonesGetCall
List(project string) ManagedZonesListCall
NewManagedZone(dnsName string) ManagedZone
}
Project interface {
// Id() string // TODO: Add as needed
// Kind() string // TODO: Add as needed
// Number() uint64 // TODO: Add as needed
// Quota() *Quota // TODO: Add as needed
// ServerResponse() googleapi.ServerResponse // TODO: Add as needed
// ForceSendFields() []string // TODO: Add as needed
}
ProjectsGetCall interface {
// TODO: Add as needed
}
ProjectsService interface {
// TODO: Add as needed
}
Quota interface {
// TODO: Add as needed
}
ResourceRecordSet interface {
// Kind() string // TODO: Add as needed
Name() string
Rrdatas() []string
Ttl() int64
Type() string
// ForceSendFields []string // TODO: Add as needed
}
ResourceRecordSetsListCall interface {
// Context(ctx context.Context) *ResourceRecordSetsListCall // TODO: Add as needed
Do(opts ...googleapi.CallOption) (ResourceRecordSetsListResponse, error)
Pages(ctx context.Context, f func(ResourceRecordSetsListResponse) error) error
// Fields(s ...googleapi.Field) *ResourceRecordSetsListCall // TODO: Add as needed
// IfNoneMatch(entityTag string) *ResourceRecordSetsListCall // TODO: Add as needed
// MaxResults(maxResults int64) *ResourceRecordSetsListCall // TODO: Add as needed
Name(name string) ResourceRecordSetsListCall
// PageToken(pageToken string) *ResourceRecordSetsListCall // TODO: Add as needed
Type(type_ string) ResourceRecordSetsListCall
}
ResourceRecordSetsListResponse interface {
// Kind() string // TODO: Add as needed
// NextPageToken() string // TODO: Add as needed
Rrsets() []ResourceRecordSet
// ServerResponse() googleapi.ServerResponse // TODO: Add as needed
// ForceSendFields() []string // TODO: Add as needed
}
ResourceRecordSetsService interface {
List(project string, managedZone string) ResourceRecordSetsListCall
// Get returns a list of resources records with the matching name
Get(project, managedZone, name string) ResourceRecordSetsListCall
// NewResourceRecordSetsService(s *Service) *ResourceRecordSetsService // TODO: add to service as needed
NewResourceRecordSet(name string, rrdatas []string, ttl int64, type_ rrstype.RrsType) ResourceRecordSet
}
Service interface {
// BasePath() string // TODO: Add as needed
// UserAgent() string // TODO: Add as needed
Changes() ChangesService
ManagedZones() ManagedZonesService
Projects() ProjectsService
ResourceRecordSets() ResourceRecordSetsService
}
// New(client *http.Client) (*Service, error) // TODO: Add as needed
)

View file

@ -0,0 +1,39 @@
/*
Copyright 2016 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 internal
import (
dns "google.golang.org/api/dns/v1"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ interfaces.ManagedZone = ManagedZone{}
type ManagedZone struct{ impl *dns.ManagedZone }
func (m ManagedZone) Name() string {
return m.impl.Name
}
func (m ManagedZone) Id() uint64 {
return m.impl.Id
}
func (m ManagedZone) DnsName() string {
return m.impl.DnsName
}

View file

@ -0,0 +1,33 @@
/*
Copyright 2016 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 internal
import (
dns "google.golang.org/api/dns/v1"
"google.golang.org/api/googleapi"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ interfaces.ManagedZonesCreateCall = ManagedZonesCreateCall{}
type ManagedZonesCreateCall struct{ impl *dns.ManagedZonesCreateCall }
func (call ManagedZonesCreateCall) Do(opts ...googleapi.CallOption) (interfaces.ManagedZone, error) {
m, err := call.impl.Do(opts...)
return &ManagedZone{m}, err
}

View file

@ -0,0 +1,32 @@
/*
Copyright 2016 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 internal
import (
dns "google.golang.org/api/dns/v1"
"google.golang.org/api/googleapi"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ interfaces.ManagedZonesDeleteCall = ManagedZonesDeleteCall{}
type ManagedZonesDeleteCall struct{ impl *dns.ManagedZonesDeleteCall }
func (call ManagedZonesDeleteCall) Do(opts ...googleapi.CallOption) error {
return call.impl.Do(opts...)
}

View file

@ -0,0 +1,33 @@
/*
Copyright 2016 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 internal
import (
dns "google.golang.org/api/dns/v1"
"google.golang.org/api/googleapi"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ interfaces.ManagedZonesGetCall = ManagedZonesGetCall{}
type ManagedZonesGetCall struct{ impl *dns.ManagedZonesGetCall }
func (call ManagedZonesGetCall) Do(opts ...googleapi.CallOption) (interfaces.ManagedZone, error) {
m, err := call.impl.Do(opts...)
return &ManagedZone{m}, err
}

View file

@ -0,0 +1,38 @@
/*
Copyright 2016 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 internal
import (
dns "google.golang.org/api/dns/v1"
"google.golang.org/api/googleapi"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ interfaces.ManagedZonesListCall = &ManagedZonesListCall{}
type ManagedZonesListCall struct{ impl *dns.ManagedZonesListCall }
func (call *ManagedZonesListCall) Do(opts ...googleapi.CallOption) (interfaces.ManagedZonesListResponse, error) {
response, err := call.impl.Do(opts...)
return &ManagedZonesListResponse{response}, err
}
func (call *ManagedZonesListCall) DnsName(dnsName string) interfaces.ManagedZonesListCall {
call.impl.DnsName(dnsName)
return call
}

View file

@ -0,0 +1,35 @@
/*
Copyright 2016 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 internal
import (
dns "google.golang.org/api/dns/v1"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ interfaces.ManagedZonesListResponse = &ManagedZonesListResponse{}
type ManagedZonesListResponse struct{ impl *dns.ManagedZonesListResponse }
func (response *ManagedZonesListResponse) ManagedZones() []interfaces.ManagedZone {
zones := make([]interfaces.ManagedZone, len(response.impl.ManagedZones))
for i, z := range response.impl.ManagedZones {
zones[i] = &ManagedZone{z}
}
return zones
}

View file

@ -0,0 +1,51 @@
/*
Copyright 2016 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 internal
import (
"strings"
dns "google.golang.org/api/dns/v1"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ interfaces.ManagedZonesService = &ManagedZonesService{}
type ManagedZonesService struct{ impl *dns.ManagedZonesService }
func (m *ManagedZonesService) Create(project string, managedzone interfaces.ManagedZone) interfaces.ManagedZonesCreateCall {
return &ManagedZonesCreateCall{m.impl.Create(project, managedzone.(*ManagedZone).impl)}
}
func (m *ManagedZonesService) Delete(project, managedZone string) interfaces.ManagedZonesDeleteCall {
return &ManagedZonesDeleteCall{m.impl.Delete(project, managedZone)}
}
func (m *ManagedZonesService) Get(project, managedZone string) interfaces.ManagedZonesGetCall {
return &ManagedZonesGetCall{m.impl.Get(project, managedZone)}
}
func (m *ManagedZonesService) List(project string) interfaces.ManagedZonesListCall {
return &ManagedZonesListCall{m.impl.List(project)}
}
func (m *ManagedZonesService) NewManagedZone(dnsName string) interfaces.ManagedZone {
name := "x" + strings.Replace(string(uuid.NewUUID()), "-", "", -1)[0:30] // Unique name, strip out the "-" chars to shorten it, start with a lower case alpha, and truncate to Cloud DNS 32 character limit
return &ManagedZone{impl: &dns.ManagedZone{Name: name, Description: "Kubernetes Federated Service", DnsName: dnsName}}
}

View file

@ -0,0 +1,32 @@
/*
Copyright 2016 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 internal
import (
dns "google.golang.org/api/dns/v1"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ interfaces.ResourceRecordSet = ResourceRecordSet{}
type ResourceRecordSet struct{ impl *dns.ResourceRecordSet }
func (r ResourceRecordSet) Name() string { return r.impl.Name }
func (r ResourceRecordSet) Rrdatas() []string { return r.impl.Rrdatas }
func (r ResourceRecordSet) Ttl() int64 { return r.impl.Ttl }
func (r ResourceRecordSet) Type() string { return r.impl.Type }

View file

@ -0,0 +1,53 @@
/*
Copyright 2016 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 internal
import (
"context"
dns "google.golang.org/api/dns/v1"
"google.golang.org/api/googleapi"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ interfaces.ResourceRecordSetsListCall = &ResourceRecordSetsListCall{}
type ResourceRecordSetsListCall struct {
impl *dns.ResourceRecordSetsListCall
}
func (call *ResourceRecordSetsListCall) Do(opts ...googleapi.CallOption) (interfaces.ResourceRecordSetsListResponse, error) {
response, err := call.impl.Do(opts...)
return &ResourceRecordSetsListResponse{response}, err
}
func (call *ResourceRecordSetsListCall) Pages(ctx context.Context, f func(interfaces.ResourceRecordSetsListResponse) error) error {
return call.impl.Pages(ctx, func(page *dns.ResourceRecordSetsListResponse) error {
return f(&ResourceRecordSetsListResponse{page})
})
}
func (call *ResourceRecordSetsListCall) Name(name string) interfaces.ResourceRecordSetsListCall {
call.impl.Name(name)
return call
}
func (call *ResourceRecordSetsListCall) Type(type_ string) interfaces.ResourceRecordSetsListCall {
call.impl.Type(type_)
return call
}

View file

@ -0,0 +1,38 @@
/*
Copyright 2016 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 internal
import (
dns "google.golang.org/api/dns/v1"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ interfaces.ResourceRecordSetsListResponse = &ResourceRecordSetsListResponse{}
type ResourceRecordSetsListResponse struct {
impl *dns.ResourceRecordSetsListResponse
}
func (response *ResourceRecordSetsListResponse) Rrsets() []interfaces.ResourceRecordSet {
rrsets := make([]interfaces.ResourceRecordSet, len(response.impl.Rrsets))
for i, rrset := range response.impl.Rrsets {
rrsets[i] = &ResourceRecordSet{rrset}
}
return rrsets
}

View file

@ -0,0 +1,43 @@
/*
Copyright 2016 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 internal
import (
dns "google.golang.org/api/dns/v1"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
"k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype"
)
// Compile time check for interface adherence
var _ interfaces.ResourceRecordSetsService = &ResourceRecordSetsService{}
type ResourceRecordSetsService struct {
impl *dns.ResourceRecordSetsService
}
func (service ResourceRecordSetsService) List(project string, managedZone string) interfaces.ResourceRecordSetsListCall {
return &ResourceRecordSetsListCall{service.impl.List(project, managedZone)}
}
func (service ResourceRecordSetsService) Get(project, managedZone, name string) interfaces.ResourceRecordSetsListCall {
return &ResourceRecordSetsListCall{service.impl.List(project, managedZone).Name(name)}
}
func (service ResourceRecordSetsService) NewResourceRecordSet(name string, rrdatas []string, ttl int64, type_ rrstype.RrsType) interfaces.ResourceRecordSet {
rrset := dns.ResourceRecordSet{Name: name, Rrdatas: rrdatas, Ttl: ttl, Type: string(type_)}
return &ResourceRecordSet{&rrset}
}

View file

@ -0,0 +1,49 @@
/*
Copyright 2016 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 internal
import (
dns "google.golang.org/api/dns/v1"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ interfaces.Service = &Service{}
type Service struct {
impl *dns.Service
}
func NewService(service *dns.Service) *Service {
return &Service{service}
}
func (s *Service) Changes() interfaces.ChangesService {
return &ChangesService{s.impl.Changes}
}
func (s *Service) ManagedZones() interfaces.ManagedZonesService {
return &ManagedZonesService{s.impl.ManagedZones}
}
func (s *Service) Projects() interfaces.ProjectsService {
return &ProjectsService{s.impl.Projects}
}
func (s *Service) ResourceRecordSets() interfaces.ResourceRecordSetsService {
return &ResourceRecordSetsService{s.impl.ResourceRecordSets}
}

View file

@ -0,0 +1,47 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"change.go",
"changes_create_call.go",
"changes_service.go",
"clouddns.go",
"managed_zone.go",
"managed_zone_create_call.go",
"managed_zones_delete_call.go",
"managed_zones_get_call.go",
"managed_zones_list_call.go",
"managed_zones_list_response.go",
"managed_zones_service.go",
"rrset.go",
"rrsets_list_call.go",
"rrsets_list_response.go",
"rrsets_service.go",
"service.go",
],
deps = [
"//federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces:go_default_library",
"//federation/pkg/dnsprovider/rrstype:go_default_library",
"//vendor/google.golang.org/api/dns/v1:go_default_library",
"//vendor/google.golang.org/api/googleapi:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,36 @@
/*
Copyright 2016 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 stubs
import "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
// Compile time check for interface adherence
var _ interfaces.Change = &Change{}
type Change struct {
Service *ChangesService
Additions_ []interfaces.ResourceRecordSet
Deletions_ []interfaces.ResourceRecordSet
}
func (c *Change) Additions() (rrsets []interfaces.ResourceRecordSet) {
return c.Additions_
}
func (c *Change) Deletions() (rrsets []interfaces.ResourceRecordSet) {
return c.Deletions_
}

View file

@ -0,0 +1,67 @@
/*
Copyright 2016 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 stubs
import (
"fmt"
"google.golang.org/api/googleapi"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ interfaces.ChangesCreateCall = ChangesCreateCall{}
type ChangesCreateCall struct {
Service *ChangesService
Project string
Zone string
Change interfaces.Change
Error error // Use this to over-ride response if necessary
}
func hashKey(set interfaces.ResourceRecordSet) string {
return fmt.Sprintf("%s-%d-%s", set.Name(), set.Ttl(), string(set.Type()))
}
func (c ChangesCreateCall) Do(opts ...googleapi.CallOption) (interfaces.Change, error) {
if c.Error != nil {
return nil, c.Error
}
zone := (c.Service.Service.ManagedZones_.Impl[c.Project][c.Zone]).(*ManagedZone)
rrsets := map[string]ResourceRecordSet{} // compute the new state
for _, set := range zone.Rrsets {
rrsets[hashKey(set)] = set
}
for _, del := range c.Change.Deletions() {
if _, found := rrsets[hashKey(del)]; !found {
return nil, fmt.Errorf("Attempt to delete non-existent rrset %v", del)
}
delete(rrsets, hashKey(del))
}
for _, add := range c.Change.Additions() {
if _, found := rrsets[hashKey(add)]; found {
return nil, fmt.Errorf("Attempt to insert duplicate rrset %v", add)
}
rrsets[hashKey(add)] = add.(ResourceRecordSet)
}
zone.Rrsets = []ResourceRecordSet{}
for _, rrset := range rrsets {
zone.Rrsets = append(zone.Rrsets, rrset)
}
return c.Change, nil
}

View file

@ -0,0 +1,34 @@
/*
Copyright 2016 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 stubs
import "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
// Compile time check for interface adherence
var _ interfaces.ChangesService = &ChangesService{}
type ChangesService struct {
Service *Service
}
func (c *ChangesService) Create(project string, managedZone string, change interfaces.Change) interfaces.ChangesCreateCall {
return &ChangesCreateCall{c, project, managedZone, change, nil}
}
func (c *ChangesService) NewChange(additions, deletions []interfaces.ResourceRecordSet) interfaces.Change {
return &Change{c, additions, deletions}
}

View file

@ -0,0 +1,33 @@
/*
Copyright 2016 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 stubs
// Implementation of internal/interfaces/* on top of Google Cloud DNS API.
// See https://godoc.org/google.golang.org/api/dns/v1 for details
// This facilitates stubbing out Google Cloud DNS for unit testing.
// Only the parts of the API that we use are included.
// Others can be added as needed.
import dns "google.golang.org/api/dns/v1"
type (
// TODO: We don't need these yet, so they remain unimplemented. Add later as required.
Project struct{ impl *dns.Project }
ProjectsGetCall struct{ impl *dns.ProjectsGetCall }
ProjectsService struct{ impl *dns.ProjectsService }
Quota struct{ impl *dns.Quota }
)

View file

@ -0,0 +1,41 @@
/*
Copyright 2016 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 stubs
import "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
// Compile time check for interface adherence
var _ interfaces.ManagedZone = ManagedZone{}
type ManagedZone struct {
Service *ManagedZonesService
Name_ string
Id_ uint64
Rrsets []ResourceRecordSet
}
func (m ManagedZone) Name() string {
return m.Name_
}
func (m ManagedZone) Id() uint64 {
return m.Id_
}
func (m ManagedZone) DnsName() string {
return m.Name_ // Don't bother storing a separate DNS name
}

View file

@ -0,0 +1,52 @@
/*
Copyright 2016 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 stubs
import (
"fmt"
"google.golang.org/api/googleapi"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ interfaces.ManagedZonesCreateCall = ManagedZonesCreateCall{}
type ManagedZonesCreateCall struct {
Error *error // Use to override response for testing
Service *ManagedZonesService
Project string
ManagedZone interfaces.ManagedZone
}
func (call ManagedZonesCreateCall) Do(opts ...googleapi.CallOption) (interfaces.ManagedZone, error) {
if call.Error != nil {
return nil, *call.Error
}
if call.Service.Impl[call.Project][call.ManagedZone.DnsName()] != nil {
return nil, fmt.Errorf("Error - attempt to create duplicate zone %s in project %s.",
call.ManagedZone.DnsName(), call.Project)
}
if call.Service.Impl == nil {
call.Service.Impl = map[string]map[string]interfaces.ManagedZone{}
}
if call.Service.Impl[call.Project] == nil {
call.Service.Impl[call.Project] = map[string]interfaces.ManagedZone{}
}
call.Service.Impl[call.Project][call.ManagedZone.DnsName()] = call.ManagedZone
return call.ManagedZone, nil
}

View file

@ -0,0 +1,53 @@
/*
Copyright 2016 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 stubs
import (
"fmt"
"google.golang.org/api/googleapi"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ interfaces.ManagedZonesDeleteCall = ManagedZonesDeleteCall{}
type ManagedZonesDeleteCall struct {
Service *ManagedZonesService
Project string
ZoneName string
Error *error // Use this to override response for testing if required
}
func (call ManagedZonesDeleteCall) Do(opts ...googleapi.CallOption) error {
if call.Error != nil { // Return the override value
return *call.Error
} else { // Just try to delete it from the in-memory array.
project, ok := call.Service.Impl[call.Project]
if ok {
zone, ok := project[call.ZoneName]
if ok {
delete(project, zone.Name())
return nil
} else {
return fmt.Errorf("Failed to find zone %s in project %s to delete it", call.ZoneName, call.Project)
}
} else {
return fmt.Errorf("Failed to find project %s to delete zone %s from it", call.Project, call.ZoneName)
}
}
}

View file

@ -0,0 +1,42 @@
/*
Copyright 2016 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 stubs
import (
"google.golang.org/api/googleapi"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ interfaces.ManagedZonesGetCall = ManagedZonesGetCall{}
type ManagedZonesGetCall struct {
Service *ManagedZonesService
Project string
ZoneName string
Response interfaces.ManagedZone // Use this to override response if required
Error *error // Use this to override response if required
DnsName_ string
}
func (call ManagedZonesGetCall) Do(opts ...googleapi.CallOption) (interfaces.ManagedZone, error) {
if call.Response != nil {
return call.Response, *call.Error
} else {
return call.Service.Impl[call.Project][call.ZoneName], nil
}
}

View file

@ -0,0 +1,59 @@
/*
Copyright 2016 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 stubs
import (
"fmt"
"google.golang.org/api/googleapi"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ interfaces.ManagedZonesListCall = &ManagedZonesListCall{}
type ManagedZonesListCall struct {
Service *ManagedZonesService
Project string
Response *interfaces.ManagedZonesListResponse // Use this to override response if required
Error *error // Use this to override response if required
DnsName_ string
}
func (call *ManagedZonesListCall) Do(opts ...googleapi.CallOption) (interfaces.ManagedZonesListResponse, error) {
if call.Response != nil {
return *call.Response, *call.Error
} else {
proj, projectFound := call.Service.Impl[call.Project]
if !projectFound {
return nil, fmt.Errorf("Project %s not found.", call.Project)
}
if call.DnsName_ != "" {
return &ManagedZonesListResponse{[]interfaces.ManagedZone{proj[call.DnsName_]}}, nil
}
list := []interfaces.ManagedZone{}
for _, zone := range proj {
list = append(list, zone)
}
return &ManagedZonesListResponse{list}, nil
}
}
func (call *ManagedZonesListCall) DnsName(dnsName string) interfaces.ManagedZonesListCall {
call.DnsName_ = dnsName
return call
}

View file

@ -0,0 +1,28 @@
/*
Copyright 2016 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 stubs
import "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
// Compile time check for interface adherence
var _ interfaces.ManagedZonesListResponse = &ManagedZonesListResponse{}
type ManagedZonesListResponse struct{ ManagedZones_ []interfaces.ManagedZone }
func (response *ManagedZonesListResponse) ManagedZones() []interfaces.ManagedZone {
return response.ManagedZones_
}

View file

@ -0,0 +1,46 @@
/*
Copyright 2016 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 stubs
import "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
// Compile time check for interface adherence
var _ interfaces.ManagedZonesService = &ManagedZonesService{}
type ManagedZonesService struct {
Impl map[string]map[string]interfaces.ManagedZone
}
func (m *ManagedZonesService) Create(project string, managedzone interfaces.ManagedZone) interfaces.ManagedZonesCreateCall {
return &ManagedZonesCreateCall{nil, m, project, managedzone.(*ManagedZone)}
}
func (m *ManagedZonesService) Delete(project string, managedZone string) interfaces.ManagedZonesDeleteCall {
return &ManagedZonesDeleteCall{m, project, managedZone, nil}
}
func (m *ManagedZonesService) Get(project string, managedZone string) interfaces.ManagedZonesGetCall {
return &ManagedZonesGetCall{m, project, managedZone, nil, nil, ""}
}
func (m *ManagedZonesService) List(project string) interfaces.ManagedZonesListCall {
return &ManagedZonesListCall{m, project, nil, nil, ""}
}
func (m *ManagedZonesService) NewManagedZone(dnsName string) interfaces.ManagedZone {
return &ManagedZone{Service: m, Name_: dnsName}
}

View file

@ -0,0 +1,34 @@
/*
Copyright 2016 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 stubs
import "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
// Compile time check for interface adherence
var _ interfaces.ResourceRecordSet = ResourceRecordSet{}
type ResourceRecordSet struct {
Name_ string
Rrdatas_ []string
Ttl_ int64
Type_ string
}
func (r ResourceRecordSet) Name() string { return r.Name_ }
func (r ResourceRecordSet) Rrdatas() []string { return r.Rrdatas_ }
func (r ResourceRecordSet) Ttl() int64 { return r.Ttl_ }
func (r ResourceRecordSet) Type() string { return r.Type_ }

View file

@ -0,0 +1,52 @@
/*
Copyright 2016 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 stubs
import (
"context"
"google.golang.org/api/googleapi"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ interfaces.ResourceRecordSetsListCall = &ResourceRecordSetsListCall{}
type ResourceRecordSetsListCall struct {
Response_ *ResourceRecordSetsListResponse
Err_ error
Name_ string
Type_ string
}
func (call *ResourceRecordSetsListCall) Do(opts ...googleapi.CallOption) (interfaces.ResourceRecordSetsListResponse, error) {
return call.Response_, call.Err_
}
func (call *ResourceRecordSetsListCall) Pages(ctx context.Context, f func(interfaces.ResourceRecordSetsListResponse) error) error {
return f(call.Response_)
}
func (call *ResourceRecordSetsListCall) Name(name string) interfaces.ResourceRecordSetsListCall {
call.Name_ = name
return call
}
func (call *ResourceRecordSetsListCall) Type(type_ string) interfaces.ResourceRecordSetsListCall {
call.Type_ = type_
return call
}

View file

@ -0,0 +1,30 @@
/*
Copyright 2016 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 stubs
import "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
// Compile time check for interface adherence
var _ interfaces.ResourceRecordSetsListResponse = &ResourceRecordSetsListResponse{}
type ResourceRecordSetsListResponse struct {
impl []interfaces.ResourceRecordSet
}
func (response *ResourceRecordSetsListResponse) Rrsets() []interfaces.ResourceRecordSet {
return response.impl
}

View file

@ -0,0 +1,83 @@
/*
Copyright 2016 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 stubs
import (
"fmt"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
"k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype"
)
// Compile time check for interface adherence
var _ interfaces.ResourceRecordSetsService = &ResourceRecordSetsService{}
type ResourceRecordSetsService struct {
Service *Service
ListCall interfaces.ResourceRecordSetsListCall // Use to override response if required for testing
}
func (s ResourceRecordSetsService) managedZone(project, managedZone string) (*ManagedZone, error) {
p := s.Service.ManagedZones_.Impl[project]
if p == nil {
return nil, fmt.Errorf("Project not found: %s", project)
}
z := s.Service.ManagedZones_.Impl[project][managedZone]
if z == nil {
return nil, fmt.Errorf("Zone %s not found in project %s", managedZone, project)
}
return z.(*ManagedZone), nil
}
func (s ResourceRecordSetsService) List(project string, managedZone string) interfaces.ResourceRecordSetsListCall {
if s.ListCall != nil {
return s.ListCall
}
zone, err := s.managedZone(project, managedZone)
if err != nil {
return &ResourceRecordSetsListCall{Err_: err}
}
response := &ResourceRecordSetsListResponse{}
for _, set := range zone.Rrsets {
response.impl = append(response.impl, set)
}
return &ResourceRecordSetsListCall{Response_: response}
}
func (s ResourceRecordSetsService) Get(project, managedZone, name string) interfaces.ResourceRecordSetsListCall {
if s.ListCall != nil {
return s.ListCall
}
zone, err := s.managedZone(project, managedZone)
if err != nil {
return &ResourceRecordSetsListCall{Err_: err}
}
response := &ResourceRecordSetsListResponse{}
for _, set := range zone.Rrsets {
if set.Name_ == name {
response.impl = append(response.impl, set)
}
}
return &ResourceRecordSetsListCall{Response_: response}
}
func (service ResourceRecordSetsService) NewResourceRecordSet(name string, rrdatas []string, ttl int64, type_ rrstype.RrsType) interfaces.ResourceRecordSet {
rrset := ResourceRecordSet{Name_: name, Rrdatas_: rrdatas, Ttl_: ttl, Type_: string(type_)}
return rrset
}

View file

@ -0,0 +1,54 @@
/*
Copyright 2016 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 stubs
import "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
// Compile time check for interface adherence
var _ interfaces.Service = &Service{}
type Service struct {
Changes_ *ChangesService
ManagedZones_ *ManagedZonesService
Projects_ *ProjectsService
Rrsets_ *ResourceRecordSetsService
}
func NewService() *Service {
s := &Service{}
s.Changes_ = &ChangesService{s}
s.ManagedZones_ = &ManagedZonesService{}
s.Projects_ = &ProjectsService{}
s.Rrsets_ = &ResourceRecordSetsService{s, nil}
return s
}
func (s *Service) Changes() interfaces.ChangesService {
return s.Changes_
}
func (s *Service) ManagedZones() interfaces.ManagedZonesService {
return s.ManagedZones_
}
func (s *Service) Projects() interfaces.ProjectsService {
return s.Projects_
}
func (s *Service) ResourceRecordSets() interfaces.ResourceRecordSetsService {
return s.Rrsets_
}

View file

@ -0,0 +1,124 @@
/*
Copyright 2016 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 clouddns
import (
"fmt"
"k8s.io/kubernetes/federation/pkg/dnsprovider"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ dnsprovider.ResourceRecordChangeset = &ResourceRecordChangeset{}
type ResourceRecordChangeset struct {
rrsets *ResourceRecordSets
additions []dnsprovider.ResourceRecordSet
removals []dnsprovider.ResourceRecordSet
upserts []dnsprovider.ResourceRecordSet
}
func (c *ResourceRecordChangeset) Add(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset {
c.additions = append(c.additions, rrset)
return c
}
func (c *ResourceRecordChangeset) Remove(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset {
c.removals = append(c.removals, rrset)
return c
}
func (c *ResourceRecordChangeset) Upsert(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset {
c.upserts = append(c.upserts, rrset)
return c
}
func (c *ResourceRecordChangeset) Apply() error {
rrsets := c.rrsets
service := rrsets.zone.zones.interface_.service.Changes()
var additions []interfaces.ResourceRecordSet
for _, r := range c.additions {
additions = append(additions, r.(ResourceRecordSet).impl)
}
var deletions []interfaces.ResourceRecordSet
for _, r := range c.removals {
deletions = append(deletions, r.(ResourceRecordSet).impl)
}
if len(c.upserts) != 0 {
// TODO: We could maybe tweak this to fetch just the records we care about
// although not clear when this would be a win. N=1 obviously so though...
before, err := c.rrsets.List()
if err != nil {
return fmt.Errorf("error fetching recordset images for upsert operation: %v", err)
}
upsertMap := make(map[string]dnsprovider.ResourceRecordSet)
for _, upsert := range c.upserts {
key := string(upsert.Type()) + "::" + upsert.Name()
upsertMap[key] = upsert
}
for _, b := range before {
key := string(b.Type()) + "::" + b.Name()
upsert := upsertMap[key]
if upsert == nil {
continue
}
deletions = append(deletions, b.(ResourceRecordSet).impl)
additions = append(additions, upsert.(ResourceRecordSet).impl)
// Mark as seen
delete(upsertMap, key)
}
// Anything left in the map must be an addition
for _, upsert := range upsertMap {
additions = append(additions, upsert.(ResourceRecordSet).impl)
}
}
change := service.NewChange(additions, deletions)
newChange, err := service.Create(rrsets.project(), rrsets.zone.impl.Name(), change).Do()
if err != nil {
return err
}
newAdditions := newChange.Additions()
if len(newAdditions) != len(additions) {
return fmt.Errorf("Internal error when adding resource record set. Call succeeded but number of records returned is incorrect. Records sent=%d, records returned=%d, additions:%v", len(additions), len(newAdditions), c.additions)
}
newDeletions := newChange.Deletions()
if len(newDeletions) != len(deletions) {
return fmt.Errorf("Internal error when deleting resource record set. Call succeeded but number of records returned is incorrect. Records sent=%d, records returned=%d, deletions:%v", len(deletions), len(newDeletions), c.removals)
}
return nil
}
func (c *ResourceRecordChangeset) IsEmpty() bool {
return len(c.additions) == 0 && len(c.removals) == 0
}
// ResourceRecordSets returns the parent ResourceRecordSets
func (c *ResourceRecordChangeset) ResourceRecordSets() dnsprovider.ResourceRecordSets {
return c.rrsets
}

View file

@ -0,0 +1,53 @@
/*
Copyright 2016 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 clouddns
import (
"fmt"
"k8s.io/kubernetes/federation/pkg/dnsprovider"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
"k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype"
)
// Compile time check for interface adherence
var _ dnsprovider.ResourceRecordSet = ResourceRecordSet{}
type ResourceRecordSet struct {
impl interfaces.ResourceRecordSet
rrsets *ResourceRecordSets
}
func (rrset ResourceRecordSet) String() string {
return fmt.Sprintf("<(clouddns) %q type=%s rrdatas=%q ttl=%v>", rrset.Name(), rrset.Type(), rrset.Rrdatas(), rrset.Ttl())
}
func (rrset ResourceRecordSet) Name() string {
return rrset.impl.Name()
}
func (rrset ResourceRecordSet) Rrdatas() []string {
return rrset.impl.Rrdatas()
}
func (rrset ResourceRecordSet) Ttl() int64 {
return rrset.impl.Ttl()
}
func (rrset ResourceRecordSet) Type() rrstype.RrsType {
return rrstype.RrsType(rrset.impl.Type())
}

View file

@ -0,0 +1,94 @@
/*
Copyright 2016 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 clouddns
import (
"context"
"k8s.io/kubernetes/federation/pkg/dnsprovider"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
"k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype"
)
// Compile time check for interface adherence
var _ dnsprovider.ResourceRecordSets = ResourceRecordSets{}
type ResourceRecordSets struct {
zone *Zone
impl interfaces.ResourceRecordSetsService
}
// List returns a list of resource records in the given project and
// managed zone.
// !!CAUTION!! Your memory might explode if you have a huge number of
// records in your managed zone.
func (rrsets ResourceRecordSets) List() ([]dnsprovider.ResourceRecordSet, error) {
var list []dnsprovider.ResourceRecordSet
ctx := context.Background()
call := rrsets.impl.List(rrsets.project(), rrsets.zone.impl.Name())
err := call.Pages(ctx, func(page interfaces.ResourceRecordSetsListResponse) error {
for _, rrset := range page.Rrsets() {
list = append(list, ResourceRecordSet{rrset, &rrsets})
}
return nil
})
if err != nil {
return nil, err
}
return list, nil
}
func (rrsets ResourceRecordSets) Get(name string) ([]dnsprovider.ResourceRecordSet, error) {
var list []dnsprovider.ResourceRecordSet
ctx := context.Background()
call := rrsets.impl.Get(rrsets.project(), rrsets.zone.impl.Name(), name)
err := call.Pages(ctx, func(page interfaces.ResourceRecordSetsListResponse) error {
for _, rrset := range page.Rrsets() {
list = append(list, ResourceRecordSet{rrset, &rrsets})
}
return nil
})
if err != nil {
return nil, err
}
return list, nil
}
func (r ResourceRecordSets) StartChangeset() dnsprovider.ResourceRecordChangeset {
return &ResourceRecordChangeset{
rrsets: &r,
}
}
func (r ResourceRecordSets) New(name string, rrdatas []string, ttl int64, rrstype rrstype.RrsType) dnsprovider.ResourceRecordSet {
return ResourceRecordSet{r.impl.NewResourceRecordSet(name, rrdatas, ttl, rrstype), &r}
}
func (rrsets ResourceRecordSets) project() string {
return rrsets.zone.project()
}
// Zone returns the parent zone
func (rrset ResourceRecordSets) Zone() dnsprovider.Zone {
return rrset.zone
}

View file

@ -0,0 +1,48 @@
/*
Copyright 2016 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 clouddns
import (
"strconv"
"k8s.io/kubernetes/federation/pkg/dnsprovider"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ dnsprovider.Zone = &Zone{}
type Zone struct {
impl interfaces.ManagedZone
zones *Zones
}
func (zone *Zone) Name() string {
return zone.impl.DnsName()
}
func (zone *Zone) ID() string {
return strconv.FormatUint(zone.impl.Id(), 10)
}
func (zone *Zone) ResourceRecordSets() (dnsprovider.ResourceRecordSets, bool) {
return &ResourceRecordSets{zone, zone.zones.interface_.service.ResourceRecordSets()}, true
}
func (zone Zone) project() string {
return zone.zones.project()
}

View file

@ -0,0 +1,68 @@
/*
Copyright 2016 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 clouddns
import (
"k8s.io/kubernetes/federation/pkg/dnsprovider"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/interfaces"
)
// Compile time check for interface adherence
var _ dnsprovider.Zones = Zones{}
type Zones struct {
impl interfaces.ManagedZonesService
interface_ *Interface
}
func (zones Zones) List() ([]dnsprovider.Zone, error) {
response, err := zones.impl.List(zones.project()).Do()
if err != nil {
return nil, err
}
managedZones := response.ManagedZones()
zoneList := make([]dnsprovider.Zone, len(managedZones))
for i, zone := range managedZones {
zoneList[i] = &Zone{zone, &zones}
}
return zoneList, nil
}
func (zones Zones) Add(zone dnsprovider.Zone) (dnsprovider.Zone, error) {
managedZone := zones.impl.NewManagedZone(zone.Name())
response, err := zones.impl.Create(zones.project(), managedZone).Do()
if err != nil {
return nil, err
}
return &Zone{response, &zones}, nil
}
func (zones Zones) Remove(zone dnsprovider.Zone) error {
if err := zones.impl.Delete(zones.project(), zone.(*Zone).impl.Name()).Do(); err != nil {
return err
}
return nil
}
func (zones Zones) New(name string) (dnsprovider.Zone, error) {
managedZone := zones.impl.NewManagedZone(name)
return &Zone{managedZone, &zones}, nil
}
func (zones Zones) project() string {
return zones.interface_.project()
}

View file

@ -0,0 +1,24 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["rrstype.go"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,28 @@
/*
Copyright 2016 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 rrstype
type (
RrsType string
)
const (
A = RrsType("A")
AAAA = RrsType("AAAA")
CNAME = RrsType("CNAME")
// TODO: Add other types as required
)

View file

@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["commontests.go"],
deps = [
"//federation/pkg/dnsprovider:go_default_library",
"//federation/pkg/dnsprovider/rrstype:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,194 @@
/*
Copyright 2016 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 tests
import (
"reflect"
"testing"
"k8s.io/kubernetes/federation/pkg/dnsprovider"
"k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype"
)
/* CommonTestResourceRecordSetsReplace verifies that replacing an RRS works */
func CommonTestResourceRecordSetsReplace(t *testing.T, zone dnsprovider.Zone) {
rrsets, _ := zone.ResourceRecordSets()
sets := rrs(t, zone)
rrset := rrsets.New("alpha.test.com", []string{"8.8.4.4"}, 40, rrstype.A)
addRrsetOrFail(t, sets, rrset)
defer sets.StartChangeset().Remove(rrset).Apply()
// Replace the record (change ttl and rrdatas)
newRrset := rrsets.New("alpha.test.com", []string{"8.8.8.8"}, 80, rrstype.A)
err := sets.StartChangeset().Add(newRrset).Remove(rrset).Apply()
if err != nil {
t.Errorf("Failed to replace resource record set %v -> %v: %v", rrset, newRrset, err)
} else {
defer sets.StartChangeset().Remove(newRrset).Apply()
t.Logf("Correctly replaced resource record %v -> %v", rrset, newRrset)
}
// Check that the record was updated
assertHasRecord(t, sets, newRrset)
}
/* CommonTestResourceRecordSetsReplaceAll verifies that we can remove an RRS and create one with a different name*/
func CommonTestResourceRecordSetsReplaceAll(t *testing.T, zone dnsprovider.Zone) {
rrsets, _ := zone.ResourceRecordSets()
sets := rrs(t, zone)
rrset := rrsets.New("alpha.test.com", []string{"8.8.4.4"}, 40, rrstype.A)
addRrsetOrFail(t, sets, rrset)
defer sets.StartChangeset().Remove(rrset).Apply()
newRrset := rrsets.New("beta.test.com", []string{"8.8.8.8"}, 80, rrstype.A)
// Try to add it again, and verify that the call fails.
err := sets.StartChangeset().Add(newRrset).Remove(rrset).Apply()
if err != nil {
t.Errorf("Failed to replace resource record set %v -> %v: %v", rrset, newRrset, err)
} else {
defer sets.StartChangeset().Remove(newRrset).Apply()
t.Logf("Correctly replaced resource record %v -> %v", rrset, newRrset)
}
// Check that it was updated
assertHasRecord(t, sets, newRrset)
assertNotHasRecord(t, sets, rrset.Name(), rrset.Type())
}
/* CommonTestResourceRecordSetsDifferentType verifies that we can add records of the same name but different types */
func CommonTestResourceRecordSetsDifferentTypes(t *testing.T, zone dnsprovider.Zone) {
rrsets, _ := zone.ResourceRecordSets()
sets := rrs(t, zone)
rrset := rrsets.New("alpha.test.com", []string{"8.8.4.4"}, 40, rrstype.A)
addRrsetOrFail(t, sets, rrset)
defer sets.StartChangeset().Remove(rrset).Apply()
aaaaRrset := rrsets.New("alpha.test.com", []string{"2001:4860:4860::8888"}, 80, rrstype.AAAA)
// Add the resource with the same name but different type
err := sets.StartChangeset().Add(aaaaRrset).Apply()
if err != nil {
t.Errorf("Failed to add resource record set %v: %v", aaaaRrset, err)
}
defer sets.StartChangeset().Remove(aaaaRrset).Apply()
// Check that both records exist
assertHasRecord(t, sets, aaaaRrset)
assertHasRecord(t, sets, rrset)
}
/* rrs returns the ResourceRecordSets interface for a given zone */
func rrs(t *testing.T, zone dnsprovider.Zone) (r dnsprovider.ResourceRecordSets) {
rrsets, supported := zone.ResourceRecordSets()
if !supported {
t.Fatalf("ResourceRecordSets interface not supported by zone %v", zone)
return r
}
return rrsets
}
func getRrOrFail(t *testing.T, rrsets dnsprovider.ResourceRecordSets, name string) []dnsprovider.ResourceRecordSet {
rrsetList, err := rrsets.Get(name)
if err != nil {
t.Fatalf("Failed to get recordset: %v", err)
} else if len(rrsetList) == 0 {
t.Logf("Did not Get recordset: %v", name)
} else {
t.Logf("Got recordset: %v", rrsetList[0].Name())
}
return rrsetList
}
// assertHasRecord tests that rrsets has a record equivalent to rrset
func assertHasRecord(t *testing.T, rrsets dnsprovider.ResourceRecordSets, rrset dnsprovider.ResourceRecordSet) {
var found dnsprovider.ResourceRecordSet
rrs, err := rrsets.List()
if err != nil {
if err.Error() == "OperationNotSupported" {
foundList := getRrOrFail(t, rrsets, rrset.Name())
for i, elem := range foundList {
if elem.Name() == rrset.Name() && elem.Type() == rrset.Type() {
found = foundList[i]
break
}
}
} else {
t.Fatalf("Failed to list recordsets: %v", err)
}
} else {
if len(rrs) < 0 {
t.Fatalf("Record set length=%d, expected >=0", len(rrs))
} else {
t.Logf("Got %d recordsets: %v", len(rrs), rrs)
}
for _, r := range rrs {
if r.Name() != rrset.Name() || r.Type() != rrset.Type() {
continue
}
if found != nil {
t.Errorf("found duplicate resource record set: %q and %q", r, found)
}
found = r
}
}
if found == nil {
t.Errorf("resource record set %v not found", rrset)
} else {
assertEquivalent(t, found, rrset)
}
}
// assertNotHasRecord tests that rrsets does not have a record matching name and type
func assertNotHasRecord(t *testing.T, rrsets dnsprovider.ResourceRecordSets, name string, rrstype rrstype.RrsType) {
found := getRrOrFail(t, rrsets, name)
if found != nil {
t.Errorf("resource record set found unexpectedly: %v", found)
}
}
// assertEquivalent tests that l is equal to r, for the methods in ResourceRecordSet
func assertEquivalent(t *testing.T, l, r dnsprovider.ResourceRecordSet) {
if l.Name() != r.Name() {
t.Errorf("resource record sets not equal %v vs %v", l, r)
}
if l.Type() != r.Type() {
t.Errorf("resource record sets not equal %v vs %v", l, r)
}
if l.Ttl() != r.Ttl() {
t.Errorf("resource record sets not equal %v vs %v", l, r)
}
if !reflect.DeepEqual(l.Rrdatas(), r.Rrdatas()) {
t.Errorf("resource record sets not equal %v vs %v", l, r)
}
}
func addRrsetOrFail(t *testing.T, rrsets dnsprovider.ResourceRecordSets, rrset dnsprovider.ResourceRecordSet) {
err := rrsets.StartChangeset().Add(rrset).Apply()
if err != nil {
t.Fatalf("Failed to add recordset %v: %v", rrset, err)
} else {
t.Logf("Successfully added resource record set: %v", rrset)
}
}

View file

@ -0,0 +1,86 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"hpa_test.go",
"scheduling_test.go",
],
library = ":go_default_library",
deps = [
"//federation/pkg/federation-controller/util/test:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/k8s.io/api/autoscaling/v1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"adapter.go",
"configmap.go",
"daemonset.go",
"deployment.go",
"hpa.go",
"namespace.go",
"qualifiedname.go",
"registry.go",
"replicaset.go",
"scheduling.go",
"secret.go",
],
deps = [
"//federation/apis/federation:go_default_library",
"//federation/apis/federation/v1beta1:go_default_library",
"//federation/client/clientset_generated/federation_clientset:go_default_library",
"//federation/pkg/federation-controller/util:go_default_library",
"//federation/pkg/federation-controller/util/hpa:go_default_library",
"//federation/pkg/federation-controller/util/planner:go_default_library",
"//federation/pkg/federation-controller/util/podanalyzer:go_default_library",
"//federation/pkg/federation-controller/util/replicapreferences:go_default_library",
"//pkg/api:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/controller/namespace/deletion:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/api/autoscaling/v1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/client-go/dynamic:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/tools/record:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//federation/pkg/federatedtypes/crudtester:all-srcs",
],
tags = ["automanaged"],
)

View file

@ -0,0 +1,79 @@
/*
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 federatedtypes
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
pkgruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
kubeclientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset"
)
// FederatedTypeAdapter defines operations for interacting with a
// federated type. Code written to this interface can then target any
// type for which an implementation of this interface exists.
type FederatedTypeAdapter interface {
Kind() string
ObjectType() pkgruntime.Object
IsExpectedType(obj interface{}) bool
Copy(obj pkgruntime.Object) pkgruntime.Object
Equivalent(obj1, obj2 pkgruntime.Object) bool
QualifiedName(obj pkgruntime.Object) QualifiedName
ObjectMeta(obj pkgruntime.Object) *metav1.ObjectMeta
// Fed* operations target the federation control plane
FedCreate(obj pkgruntime.Object) (pkgruntime.Object, error)
FedDelete(qualifiedName QualifiedName, options *metav1.DeleteOptions) error
FedGet(qualifiedName QualifiedName) (pkgruntime.Object, error)
FedList(namespace string, options metav1.ListOptions) (pkgruntime.Object, error)
FedUpdate(obj pkgruntime.Object) (pkgruntime.Object, error)
FedWatch(namespace string, options metav1.ListOptions) (watch.Interface, error)
// The following operations are intended to target a cluster that is a member of a federation
ClusterCreate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error)
ClusterDelete(client kubeclientset.Interface, qualifiedName QualifiedName, options *metav1.DeleteOptions) error
ClusterGet(client kubeclientset.Interface, qualifiedName QualifiedName) (pkgruntime.Object, error)
ClusterList(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (pkgruntime.Object, error)
ClusterUpdate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error)
ClusterWatch(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (watch.Interface, error)
IsSchedulingAdapter() bool
NewTestObject(namespace string) pkgruntime.Object
}
// AdapterFactory defines the function signature for factory methods
// that create instances of FederatedTypeAdapter. Such methods should
// be registered with RegisterAdapterFactory to ensure the type
// adapter is discoverable.
type AdapterFactory func(client federationclientset.Interface, config *restclient.Config, adapterSpecificArgs map[string]interface{}) FederatedTypeAdapter
// SetAnnotation sets the given key and value in the given object's ObjectMeta.Annotations map
func SetAnnotation(adapter FederatedTypeAdapter, obj pkgruntime.Object, key, value string) {
meta := adapter.ObjectMeta(obj)
if meta.Annotations == nil {
meta.Annotations = make(map[string]string)
}
meta.Annotations[key] = value
}
// ObjectKey returns a cluster-unique key for the given object
func ObjectKey(adapter FederatedTypeAdapter, obj pkgruntime.Object) string {
return adapter.QualifiedName(obj).String()
}

View file

@ -0,0 +1,150 @@
/*
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 federatedtypes
import (
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
pkgruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
kubeclientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset"
"k8s.io/kubernetes/federation/pkg/federation-controller/util"
)
const (
ConfigMapKind = "configmap"
ConfigMapControllerName = "configmaps"
)
func init() {
RegisterFederatedType(ConfigMapKind, ConfigMapControllerName, []schema.GroupVersionResource{apiv1.SchemeGroupVersion.WithResource(ConfigMapControllerName)}, NewConfigMapAdapter)
}
type ConfigMapAdapter struct {
client federationclientset.Interface
}
func NewConfigMapAdapter(client federationclientset.Interface, config *restclient.Config, adapterSpecificArgs map[string]interface{}) FederatedTypeAdapter {
return &ConfigMapAdapter{client: client}
}
func (a *ConfigMapAdapter) Kind() string {
return ConfigMapKind
}
func (a *ConfigMapAdapter) ObjectType() pkgruntime.Object {
return &apiv1.ConfigMap{}
}
func (a *ConfigMapAdapter) IsExpectedType(obj interface{}) bool {
_, ok := obj.(*apiv1.ConfigMap)
return ok
}
func (a *ConfigMapAdapter) Copy(obj pkgruntime.Object) pkgruntime.Object {
configmap := obj.(*apiv1.ConfigMap)
return &apiv1.ConfigMap{
ObjectMeta: util.DeepCopyRelevantObjectMeta(configmap.ObjectMeta),
Data: configmap.Data,
}
}
func (a *ConfigMapAdapter) Equivalent(obj1, obj2 pkgruntime.Object) bool {
configmap1 := obj1.(*apiv1.ConfigMap)
configmap2 := obj2.(*apiv1.ConfigMap)
return util.ConfigMapEquivalent(configmap1, configmap2)
}
func (a *ConfigMapAdapter) QualifiedName(obj pkgruntime.Object) QualifiedName {
configmap := obj.(*apiv1.ConfigMap)
return QualifiedName{Namespace: configmap.Namespace, Name: configmap.Name}
}
func (a *ConfigMapAdapter) ObjectMeta(obj pkgruntime.Object) *metav1.ObjectMeta {
return &obj.(*apiv1.ConfigMap).ObjectMeta
}
func (a *ConfigMapAdapter) FedCreate(obj pkgruntime.Object) (pkgruntime.Object, error) {
configmap := obj.(*apiv1.ConfigMap)
return a.client.CoreV1().ConfigMaps(configmap.Namespace).Create(configmap)
}
func (a *ConfigMapAdapter) FedDelete(qualifiedName QualifiedName, options *metav1.DeleteOptions) error {
return a.client.CoreV1().ConfigMaps(qualifiedName.Namespace).Delete(qualifiedName.Name, options)
}
func (a *ConfigMapAdapter) FedGet(qualifiedName QualifiedName) (pkgruntime.Object, error) {
return a.client.CoreV1().ConfigMaps(qualifiedName.Namespace).Get(qualifiedName.Name, metav1.GetOptions{})
}
func (a *ConfigMapAdapter) FedList(namespace string, options metav1.ListOptions) (pkgruntime.Object, error) {
return a.client.CoreV1().ConfigMaps(namespace).List(options)
}
func (a *ConfigMapAdapter) FedUpdate(obj pkgruntime.Object) (pkgruntime.Object, error) {
configmap := obj.(*apiv1.ConfigMap)
return a.client.CoreV1().ConfigMaps(configmap.Namespace).Update(configmap)
}
func (a *ConfigMapAdapter) FedWatch(namespace string, options metav1.ListOptions) (watch.Interface, error) {
return a.client.CoreV1().ConfigMaps(namespace).Watch(options)
}
func (a *ConfigMapAdapter) ClusterCreate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) {
configmap := obj.(*apiv1.ConfigMap)
return client.CoreV1().ConfigMaps(configmap.Namespace).Create(configmap)
}
func (a *ConfigMapAdapter) ClusterDelete(client kubeclientset.Interface, qualifiedName QualifiedName, options *metav1.DeleteOptions) error {
return client.CoreV1().ConfigMaps(qualifiedName.Namespace).Delete(qualifiedName.Name, options)
}
func (a *ConfigMapAdapter) ClusterGet(client kubeclientset.Interface, qualifiedName QualifiedName) (pkgruntime.Object, error) {
return client.CoreV1().ConfigMaps(qualifiedName.Namespace).Get(qualifiedName.Name, metav1.GetOptions{})
}
func (a *ConfigMapAdapter) ClusterList(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (pkgruntime.Object, error) {
return client.CoreV1().ConfigMaps(namespace).List(options)
}
func (a *ConfigMapAdapter) ClusterUpdate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) {
configmap := obj.(*apiv1.ConfigMap)
return client.CoreV1().ConfigMaps(configmap.Namespace).Update(configmap)
}
func (a *ConfigMapAdapter) ClusterWatch(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (watch.Interface, error) {
return client.CoreV1().ConfigMaps(namespace).Watch(options)
}
func (a *ConfigMapAdapter) IsSchedulingAdapter() bool {
return false
}
func (a *ConfigMapAdapter) NewTestObject(namespace string) pkgruntime.Object {
return &apiv1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-configmap-",
Namespace: namespace,
},
Data: map[string]string{
"A": "ala ma kota",
},
}
}

View file

@ -0,0 +1,32 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["crudtester.go"],
deps = [
"//federation/pkg/federatedtypes:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,247 @@
/*
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 crudtester
import (
"fmt"
"time"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
pkgruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/federation/pkg/federatedtypes"
)
const (
AnnotationTestFederationCRUDUpdate string = "federation.kubernetes.io/test-federation-crud-update"
)
// TestLogger defines operations common across different types of testing
type TestLogger interface {
Fatalf(format string, args ...interface{})
Fatal(msg string)
Logf(format string, args ...interface{})
}
// FederatedTypeCRUDTester exercises Create/Read/Update/Delete operations for
// federated types via the Federation API and validates that the
// results of those operations are propagated to clusters that are
// members of a federation.
type FederatedTypeCRUDTester struct {
tl TestLogger
adapter federatedtypes.FederatedTypeAdapter
kind string
clusterClients []clientset.Interface
waitInterval time.Duration
// Federation operations will use wait.ForeverTestTimeout. Any
// operation that involves member clusters may take longer due to
// propagation latency.
clusterWaitTimeout time.Duration
}
func NewFederatedTypeCRUDTester(testLogger TestLogger, adapter federatedtypes.FederatedTypeAdapter, clusterClients []clientset.Interface, waitInterval, clusterWaitTimeout time.Duration) *FederatedTypeCRUDTester {
return &FederatedTypeCRUDTester{
tl: testLogger,
adapter: adapter,
kind: adapter.Kind(),
clusterClients: clusterClients,
waitInterval: waitInterval,
clusterWaitTimeout: clusterWaitTimeout,
}
}
func (c *FederatedTypeCRUDTester) CheckLifecycle(desiredObject pkgruntime.Object) {
obj := c.CheckCreate(desiredObject)
c.CheckUpdate(obj)
// Validate the golden path - removal of dependents
orphanDependents := false
c.CheckDelete(obj, &orphanDependents)
}
func (c *FederatedTypeCRUDTester) Create(desiredObject pkgruntime.Object) pkgruntime.Object {
namespace := c.adapter.ObjectMeta(desiredObject).Namespace
resourceMsg := fmt.Sprintf("federated %s", c.kind)
if len(namespace) > 0 {
resourceMsg = fmt.Sprintf("%s in namespace %q", resourceMsg, namespace)
}
c.tl.Logf("Creating new %s", resourceMsg)
obj, err := c.adapter.FedCreate(desiredObject)
if err != nil {
c.tl.Fatalf("Error creating %s: %v", resourceMsg, err)
}
qualifiedName := c.adapter.QualifiedName(obj)
c.tl.Logf("Created new federated %s %q", c.kind, qualifiedName)
return obj
}
func (c *FederatedTypeCRUDTester) CheckCreate(desiredObject pkgruntime.Object) pkgruntime.Object {
obj := c.Create(desiredObject)
c.CheckPropagation(obj)
return obj
}
func (c *FederatedTypeCRUDTester) CheckUpdate(obj pkgruntime.Object) {
qualifiedName := c.adapter.QualifiedName(obj)
var initialAnnotation string
meta := c.adapter.ObjectMeta(obj)
if meta.Annotations != nil {
initialAnnotation = meta.Annotations[AnnotationTestFederationCRUDUpdate]
}
c.tl.Logf("Updating federated %s %q", c.kind, qualifiedName)
updatedObj, err := c.updateFedObject(obj)
if err != nil {
c.tl.Fatalf("Error updating federated %s %q: %v", c.kind, qualifiedName, err)
}
// updateFedObject is expected to have changed the value of the annotation
meta = c.adapter.ObjectMeta(updatedObj)
updatedAnnotation := meta.Annotations[AnnotationTestFederationCRUDUpdate]
if updatedAnnotation == initialAnnotation {
c.tl.Fatalf("Federated %s %q not mutated", c.kind, qualifiedName)
}
c.CheckPropagation(updatedObj)
}
func (c *FederatedTypeCRUDTester) CheckDelete(obj pkgruntime.Object, orphanDependents *bool) {
qualifiedName := c.adapter.QualifiedName(obj)
c.tl.Logf("Deleting federated %s %q", c.kind, qualifiedName)
err := c.adapter.FedDelete(qualifiedName, &metav1.DeleteOptions{OrphanDependents: orphanDependents})
if err != nil {
c.tl.Fatalf("Error deleting federated %s %q: %v", c.kind, qualifiedName, err)
}
deletingInCluster := (orphanDependents != nil && *orphanDependents == false)
waitTimeout := wait.ForeverTestTimeout
if deletingInCluster {
// May need extra time to delete both federation and cluster resources
waitTimeout = c.clusterWaitTimeout
}
// Wait for deletion. The federation resource will only be removed once orphan deletion has been
// completed or deemed unnecessary.
err = wait.PollImmediate(c.waitInterval, waitTimeout, func() (bool, error) {
_, err := c.adapter.FedGet(qualifiedName)
if errors.IsNotFound(err) {
return true, nil
}
return false, err
})
if err != nil {
c.tl.Fatalf("Error deleting federated %s %q: %v", c.kind, qualifiedName, err)
}
var stateMsg string = "present"
if deletingInCluster {
stateMsg = "not present"
}
for _, client := range c.clusterClients {
_, err := c.adapter.ClusterGet(client, qualifiedName)
switch {
case !deletingInCluster && errors.IsNotFound(err):
c.tl.Fatalf("Federated %s %q was unexpectedly deleted from a member cluster", c.kind, qualifiedName)
case deletingInCluster && err == nil:
c.tl.Fatalf("Federated %s %q was unexpectedly orphaned in a member cluster", c.kind, qualifiedName)
case err != nil && !errors.IsNotFound(err):
c.tl.Fatalf("Error while checking whether %s %q is %s in member clusters: %v", c.kind, qualifiedName, stateMsg, err)
}
}
}
// CheckPropagation checks propagation for the crud tester's clients
func (c *FederatedTypeCRUDTester) CheckPropagation(obj pkgruntime.Object) {
c.CheckPropagationForClients(obj, c.clusterClients, true)
}
// CheckPropagationForClients checks propagation for the provided clients
func (c *FederatedTypeCRUDTester) CheckPropagationForClients(obj pkgruntime.Object, clusterClients []clientset.Interface, objExpected bool) {
qualifiedName := c.adapter.QualifiedName(obj)
c.tl.Logf("Waiting for %s %q in %d clusters", c.kind, qualifiedName, len(clusterClients))
for _, client := range clusterClients {
err := c.waitForResource(client, obj)
switch {
case err == wait.ErrWaitTimeout:
if objExpected {
c.tl.Fatalf("Timeout verifying %s %q in a member cluster: %v", c.kind, qualifiedName, err)
}
case err != nil:
c.tl.Fatalf("Failed to verify %s %q in a member cluster: %v", c.kind, qualifiedName, err)
case err == nil && !objExpected:
c.tl.Fatalf("Found unexpected object %s %q in a member cluster: %v", c.kind, qualifiedName, err)
}
}
}
func (c *FederatedTypeCRUDTester) waitForResource(client clientset.Interface, obj pkgruntime.Object) error {
qualifiedName := c.adapter.QualifiedName(obj)
err := wait.PollImmediate(c.waitInterval, c.clusterWaitTimeout, func() (bool, error) {
equivalenceFunc := c.adapter.Equivalent
if c.adapter.IsSchedulingAdapter() {
schedulingAdapter, ok := c.adapter.(federatedtypes.SchedulingAdapter)
if !ok {
c.tl.Fatalf("Adapter for kind %q does not properly implement SchedulingAdapter.", c.adapter.Kind())
}
equivalenceFunc = schedulingAdapter.EquivalentIgnoringSchedule
}
clusterObj, err := c.adapter.ClusterGet(client, qualifiedName)
if err == nil && equivalenceFunc(clusterObj, obj) {
return true, nil
}
if errors.IsNotFound(err) {
return false, nil
}
return false, err
})
return err
}
func (c *FederatedTypeCRUDTester) updateFedObject(obj pkgruntime.Object) (pkgruntime.Object, error) {
err := wait.PollImmediate(c.waitInterval, wait.ForeverTestTimeout, func() (bool, error) {
// Target the metadata for simplicity (it's type-agnostic)
federatedtypes.SetAnnotation(c.adapter, obj, AnnotationTestFederationCRUDUpdate, "updated")
_, err := c.adapter.FedUpdate(obj)
if errors.IsConflict(err) {
// The resource was updated by the federation controller.
// Get the latest version and retry.
qualifiedName := c.adapter.QualifiedName(obj)
obj, err = c.adapter.FedGet(qualifiedName)
return false, err
}
// Be tolerant of a slow server
if errors.IsServerTimeout(err) {
return false, nil
}
return (err == nil), err
})
return obj, err
}

View file

@ -0,0 +1,167 @@
/*
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 federatedtypes
import (
"reflect"
"k8s.io/api/core/v1"
extensionsv1 "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
pkgruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
kubeclientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset"
"k8s.io/kubernetes/federation/pkg/federation-controller/util"
)
const (
DaemonSetKind = "daemonset"
DaemonSetControllerName = "daemonsets"
)
func init() {
RegisterFederatedType(DaemonSetKind, DaemonSetControllerName, []schema.GroupVersionResource{extensionsv1.SchemeGroupVersion.WithResource(DaemonSetControllerName)}, NewDaemonSetAdapter)
}
type DaemonSetAdapter struct {
client federationclientset.Interface
}
func NewDaemonSetAdapter(client federationclientset.Interface, config *restclient.Config, adapterSpecificArgs map[string]interface{}) FederatedTypeAdapter {
return &DaemonSetAdapter{client: client}
}
func (a *DaemonSetAdapter) Kind() string {
return DaemonSetKind
}
func (a *DaemonSetAdapter) ObjectType() pkgruntime.Object {
return &extensionsv1.DaemonSet{}
}
func (a *DaemonSetAdapter) IsExpectedType(obj interface{}) bool {
_, ok := obj.(*extensionsv1.DaemonSet)
return ok
}
func (a *DaemonSetAdapter) Copy(obj pkgruntime.Object) pkgruntime.Object {
daemonset := obj.(*extensionsv1.DaemonSet)
return &extensionsv1.DaemonSet{
ObjectMeta: util.DeepCopyRelevantObjectMeta(daemonset.ObjectMeta),
Spec: *(util.DeepCopyApiTypeOrPanic(&daemonset.Spec).(*extensionsv1.DaemonSetSpec)),
}
}
func (a *DaemonSetAdapter) Equivalent(obj1, obj2 pkgruntime.Object) bool {
daemonset1 := obj1.(*extensionsv1.DaemonSet)
daemonset2 := obj2.(*extensionsv1.DaemonSet)
return util.ObjectMetaEquivalent(daemonset1.ObjectMeta, daemonset2.ObjectMeta) && reflect.DeepEqual(daemonset1.Spec, daemonset2.Spec)
}
func (a *DaemonSetAdapter) QualifiedName(obj pkgruntime.Object) QualifiedName {
daemonset := obj.(*extensionsv1.DaemonSet)
return QualifiedName{Namespace: daemonset.Namespace, Name: daemonset.Name}
}
func (a *DaemonSetAdapter) ObjectMeta(obj pkgruntime.Object) *metav1.ObjectMeta {
return &obj.(*extensionsv1.DaemonSet).ObjectMeta
}
func (a *DaemonSetAdapter) FedCreate(obj pkgruntime.Object) (pkgruntime.Object, error) {
daemonset := obj.(*extensionsv1.DaemonSet)
return a.client.Extensions().DaemonSets(daemonset.Namespace).Create(daemonset)
}
func (a *DaemonSetAdapter) FedDelete(qualifiedName QualifiedName, options *metav1.DeleteOptions) error {
return a.client.Extensions().DaemonSets(qualifiedName.Namespace).Delete(qualifiedName.Name, options)
}
func (a *DaemonSetAdapter) FedGet(qualifiedName QualifiedName) (pkgruntime.Object, error) {
return a.client.Extensions().DaemonSets(qualifiedName.Namespace).Get(qualifiedName.Name, metav1.GetOptions{})
}
func (a *DaemonSetAdapter) FedList(namespace string, options metav1.ListOptions) (pkgruntime.Object, error) {
return a.client.Extensions().DaemonSets(namespace).List(options)
}
func (a *DaemonSetAdapter) FedUpdate(obj pkgruntime.Object) (pkgruntime.Object, error) {
daemonset := obj.(*extensionsv1.DaemonSet)
return a.client.Extensions().DaemonSets(daemonset.Namespace).Update(daemonset)
}
func (a *DaemonSetAdapter) FedWatch(namespace string, options metav1.ListOptions) (watch.Interface, error) {
return a.client.Extensions().DaemonSets(namespace).Watch(options)
}
func (a *DaemonSetAdapter) ClusterCreate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) {
daemonset := obj.(*extensionsv1.DaemonSet)
return client.Extensions().DaemonSets(daemonset.Namespace).Create(daemonset)
}
func (a *DaemonSetAdapter) ClusterDelete(client kubeclientset.Interface, qualifiedName QualifiedName, options *metav1.DeleteOptions) error {
return client.Extensions().DaemonSets(qualifiedName.Namespace).Delete(qualifiedName.Name, options)
}
func (a *DaemonSetAdapter) ClusterGet(client kubeclientset.Interface, qualifiedName QualifiedName) (pkgruntime.Object, error) {
return client.Extensions().DaemonSets(qualifiedName.Namespace).Get(qualifiedName.Name, metav1.GetOptions{})
}
func (a *DaemonSetAdapter) ClusterList(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (pkgruntime.Object, error) {
return client.Extensions().DaemonSets(namespace).List(options)
}
func (a *DaemonSetAdapter) ClusterUpdate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) {
daemonset := obj.(*extensionsv1.DaemonSet)
return client.Extensions().DaemonSets(daemonset.Namespace).Update(daemonset)
}
func (a *DaemonSetAdapter) ClusterWatch(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (watch.Interface, error) {
return client.Extensions().DaemonSets(namespace).Watch(options)
}
func (a *DaemonSetAdapter) IsSchedulingAdapter() bool {
return false
}
func (a *DaemonSetAdapter) NewTestObject(namespace string) pkgruntime.Object {
return &extensionsv1.DaemonSet{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-daemonset-",
Namespace: namespace,
Labels: map[string]string{"app": "test-daemonset"},
},
Spec: extensionsv1.DaemonSetSpec{
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"name": "test-pod"},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "test-daemonset",
Image: "images/test-daemonset",
Ports: []v1.ContainerPort{{ContainerPort: 9376}},
},
},
},
},
},
}
}

View file

@ -0,0 +1,189 @@
/*
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 federatedtypes
import (
apiv1 "k8s.io/api/core/v1"
extensionsv1 "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
pkgruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
kubeclientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset"
fedutil "k8s.io/kubernetes/federation/pkg/federation-controller/util"
)
const (
DeploymentKind = "deployment"
DeploymentControllerName = "deployments"
FedDeploymentPreferencesAnnotation = "federation.kubernetes.io/deployment-preferences"
)
func init() {
RegisterFederatedType(DeploymentKind, DeploymentControllerName, []schema.GroupVersionResource{extensionsv1.SchemeGroupVersion.WithResource(DeploymentControllerName)}, NewDeploymentAdapter)
}
type DeploymentAdapter struct {
*replicaSchedulingAdapter
client federationclientset.Interface
}
func NewDeploymentAdapter(client federationclientset.Interface, config *restclient.Config, adapterSpecificArgs map[string]interface{}) FederatedTypeAdapter {
schedulingAdapter := replicaSchedulingAdapter{
preferencesAnnotationName: FedDeploymentPreferencesAnnotation,
updateStatusFunc: func(obj pkgruntime.Object, schedulingInfo interface{}) error {
deployment := obj.(*extensionsv1.Deployment)
typedStatus := schedulingInfo.(*ReplicaSchedulingInfo).Status
if typedStatus.Replicas != deployment.Status.Replicas || typedStatus.UpdatedReplicas != deployment.Status.UpdatedReplicas ||
typedStatus.ReadyReplicas != deployment.Status.ReadyReplicas || typedStatus.AvailableReplicas != deployment.Status.AvailableReplicas {
deployment.Status = extensionsv1.DeploymentStatus{
Replicas: typedStatus.Replicas,
UpdatedReplicas: typedStatus.UpdatedReplicas,
ReadyReplicas: typedStatus.ReadyReplicas,
AvailableReplicas: typedStatus.AvailableReplicas,
}
_, err := client.Extensions().Deployments(deployment.Namespace).UpdateStatus(deployment)
return err
}
return nil
},
}
return &DeploymentAdapter{&schedulingAdapter, client}
}
func (a *DeploymentAdapter) Kind() string {
return DeploymentKind
}
func (a *DeploymentAdapter) ObjectType() pkgruntime.Object {
return &extensionsv1.Deployment{}
}
func (a *DeploymentAdapter) IsExpectedType(obj interface{}) bool {
_, ok := obj.(*extensionsv1.Deployment)
return ok
}
func (a *DeploymentAdapter) Copy(obj pkgruntime.Object) pkgruntime.Object {
deployment := obj.(*extensionsv1.Deployment)
return fedutil.DeepCopyDeployment(deployment)
}
func (a *DeploymentAdapter) Equivalent(obj1, obj2 pkgruntime.Object) bool {
deployment1 := obj1.(*extensionsv1.Deployment)
deployment2 := obj2.(*extensionsv1.Deployment)
return fedutil.DeploymentEquivalent(deployment1, deployment2)
}
func (a *DeploymentAdapter) QualifiedName(obj pkgruntime.Object) QualifiedName {
deployment := obj.(*extensionsv1.Deployment)
return QualifiedName{Namespace: deployment.Namespace, Name: deployment.Name}
}
func (a *DeploymentAdapter) ObjectMeta(obj pkgruntime.Object) *metav1.ObjectMeta {
return &obj.(*extensionsv1.Deployment).ObjectMeta
}
func (a *DeploymentAdapter) FedCreate(obj pkgruntime.Object) (pkgruntime.Object, error) {
deployment := obj.(*extensionsv1.Deployment)
return a.client.Extensions().Deployments(deployment.Namespace).Create(deployment)
}
func (a *DeploymentAdapter) FedDelete(qualifiedName QualifiedName, options *metav1.DeleteOptions) error {
return a.client.Extensions().Deployments(qualifiedName.Namespace).Delete(qualifiedName.Name, options)
}
func (a *DeploymentAdapter) FedGet(qualifiedName QualifiedName) (pkgruntime.Object, error) {
return a.client.Extensions().Deployments(qualifiedName.Namespace).Get(qualifiedName.Name, metav1.GetOptions{})
}
func (a *DeploymentAdapter) FedList(namespace string, options metav1.ListOptions) (pkgruntime.Object, error) {
return a.client.Extensions().Deployments(namespace).List(options)
}
func (a *DeploymentAdapter) FedUpdate(obj pkgruntime.Object) (pkgruntime.Object, error) {
deployment := obj.(*extensionsv1.Deployment)
return a.client.Extensions().Deployments(deployment.Namespace).Update(deployment)
}
func (a *DeploymentAdapter) FedWatch(namespace string, options metav1.ListOptions) (watch.Interface, error) {
return a.client.Extensions().Deployments(namespace).Watch(options)
}
func (a *DeploymentAdapter) ClusterCreate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) {
deployment := obj.(*extensionsv1.Deployment)
return client.Extensions().Deployments(deployment.Namespace).Create(deployment)
}
func (a *DeploymentAdapter) ClusterDelete(client kubeclientset.Interface, qualifiedName QualifiedName, options *metav1.DeleteOptions) error {
return client.Extensions().Deployments(qualifiedName.Namespace).Delete(qualifiedName.Name, options)
}
func (a *DeploymentAdapter) ClusterGet(client kubeclientset.Interface, qualifiedName QualifiedName) (pkgruntime.Object, error) {
return client.Extensions().Deployments(qualifiedName.Namespace).Get(qualifiedName.Name, metav1.GetOptions{})
}
func (a *DeploymentAdapter) ClusterList(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (pkgruntime.Object, error) {
return client.Extensions().Deployments(namespace).List(options)
}
func (a *DeploymentAdapter) ClusterUpdate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) {
deployment := obj.(*extensionsv1.Deployment)
return client.Extensions().Deployments(deployment.Namespace).Update(deployment)
}
func (a *DeploymentAdapter) ClusterWatch(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (watch.Interface, error) {
return client.Extensions().Deployments(namespace).Watch(options)
}
func (a *DeploymentAdapter) EquivalentIgnoringSchedule(obj1, obj2 pkgruntime.Object) bool {
deployment1 := obj1.(*extensionsv1.Deployment)
deployment2 := a.Copy(obj2).(*extensionsv1.Deployment)
deployment2.Spec.Replicas = deployment1.Spec.Replicas
return fedutil.DeploymentEquivalent(deployment1, deployment2)
}
func (a *DeploymentAdapter) NewTestObject(namespace string) pkgruntime.Object {
replicas := int32(3)
zero := int64(0)
return &extensionsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-deployment-",
Namespace: namespace,
},
Spec: extensionsv1.DeploymentSpec{
Replicas: &replicas,
Template: apiv1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"foo": "bar"},
},
Spec: apiv1.PodSpec{
TerminationGracePeriodSeconds: &zero,
Containers: []apiv1.Container{
{
Name: "nginx",
Image: "nginx",
},
},
},
},
},
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,264 @@
/*
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 federatedtypes
import (
"testing"
autoscalingv1 "k8s.io/api/autoscaling/v1"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
pkgruntime "k8s.io/apimachinery/pkg/runtime"
. "k8s.io/kubernetes/federation/pkg/federation-controller/util/test"
"github.com/stretchr/testify/assert"
)
type replicas struct {
min int32
max int32
}
func TestGetHpaScheduleState(t *testing.T) {
defaultFedHpa := newHpaWithReplicas(NewInt32(1), NewInt32(70), 10)
testCases := map[string]struct {
fedHpa *autoscalingv1.HorizontalPodAutoscaler
localHpas map[string]pkgruntime.Object
expectedReplicas map[string]*replicas
}{
"Distribiutes replicas randomly if no existing hpa in any local cluster": {
localHpas: func() map[string]pkgruntime.Object {
hpas := make(map[string]pkgruntime.Object)
hpas["c1"] = nil
hpas["c2"] = nil
return hpas
}(),
},
"Cluster with no hpa gets replicas if other clusters have replicas": {
localHpas: func() map[string]pkgruntime.Object {
hpas := make(map[string]pkgruntime.Object)
hpas["c1"] = newHpaWithReplicas(NewInt32(1), NewInt32(70), 10)
hpas["c2"] = nil
return hpas
}(),
expectedReplicas: map[string]*replicas{
"c1": {
min: int32(1),
max: int32(9),
},
"c2": {
min: int32(1),
max: int32(1),
},
},
},
"Cluster needing max replicas gets it if there is another cluster to offer max": {
localHpas: func() map[string]pkgruntime.Object {
hpa1 := newHpaWithReplicas(NewInt32(1), NewInt32(70), 7)
hpa1 = updateHpaStatus(hpa1, NewInt32(50), 5, 5, true)
hpa2 := newHpaWithReplicas(NewInt32(1), NewInt32(70), 1)
hpa2 = updateHpaStatus(hpa2, NewInt32(70), 1, 1, true)
// include third object to ensure, it does not break the test
hpa3 := newHpaWithReplicas(NewInt32(1), NewInt32(70), 2)
hpa3 = updateHpaStatus(hpa3, NewInt32(70), 1, 1, false)
hpas := make(map[string]pkgruntime.Object)
hpas["c1"] = hpa1
hpas["c2"] = hpa2
hpas["c3"] = hpa3
return hpas
}(),
expectedReplicas: map[string]*replicas{
"c1": {
min: int32(1),
max: int32(6),
},
"c2": {
min: int32(1),
max: int32(2),
},
"c3": {
min: int32(1),
max: int32(2),
},
},
},
"Cluster needing max replicas does not get it if there is no cluster offerring max": {
localHpas: func() map[string]pkgruntime.Object {
hpa1 := newHpaWithReplicas(NewInt32(1), NewInt32(70), 9)
hpa1 = updateHpaStatus(hpa1, NewInt32(70), 9, 9, false)
hpa2 := newHpaWithReplicas(NewInt32(1), NewInt32(70), 1)
hpa2 = updateHpaStatus(hpa2, NewInt32(70), 1, 1, true)
hpas := make(map[string]pkgruntime.Object)
hpas["c1"] = hpa1
hpas["c2"] = hpa2
return hpas
}(),
expectedReplicas: map[string]*replicas{
"c1": {
min: int32(1),
max: int32(9),
},
"c2": {
min: int32(1),
max: int32(1),
},
},
},
"Cluster which can increase min replicas gets to increase min if there is a cluster offering min": {
fedHpa: newHpaWithReplicas(NewInt32(4), NewInt32(70), 10),
localHpas: func() map[string]pkgruntime.Object {
hpa1 := newHpaWithReplicas(NewInt32(3), NewInt32(70), 6)
hpa1 = updateHpaStatus(hpa1, NewInt32(50), 3, 3, true)
hpa2 := newHpaWithReplicas(NewInt32(1), NewInt32(70), 4)
hpa2 = updateHpaStatus(hpa2, NewInt32(50), 3, 3, true)
hpas := make(map[string]pkgruntime.Object)
hpas["c1"] = hpa1
hpas["c2"] = hpa2
return hpas
}(),
expectedReplicas: map[string]*replicas{
"c1": {
min: int32(2),
max: int32(6),
},
"c2": {
min: int32(2),
max: int32(4),
},
},
},
"Cluster which can increase min replicas does not increase if there are no clusters offering min": {
fedHpa: newHpaWithReplicas(NewInt32(4), NewInt32(70), 10),
localHpas: func() map[string]pkgruntime.Object {
hpa1 := newHpaWithReplicas(NewInt32(3), NewInt32(70), 6)
hpa1 = updateHpaStatus(hpa1, NewInt32(50), 4, 4, true)
hpa2 := newHpaWithReplicas(NewInt32(1), NewInt32(70), 4)
hpa2 = updateHpaStatus(hpa2, NewInt32(50), 3, 3, true)
hpas := make(map[string]pkgruntime.Object)
hpas["c1"] = hpa1
hpas["c2"] = hpa2
return hpas
}(),
expectedReplicas: map[string]*replicas{
"c1": {
min: int32(3),
max: int32(6),
},
"c2": {
min: int32(1),
max: int32(4),
},
},
},
"Increasing replicas on fed object increases the same on clusters": {
// Existing total of local min, max = 1+1, 5+5 decreasing to below
fedHpa: newHpaWithReplicas(NewInt32(4), NewInt32(70), 14),
localHpas: func() map[string]pkgruntime.Object {
// does not matter if scaleability is true
hpas := make(map[string]pkgruntime.Object)
hpas["c1"] = newHpaWithReplicas(NewInt32(1), NewInt32(70), 5)
hpas["c2"] = newHpaWithReplicas(NewInt32(1), NewInt32(70), 5)
return hpas
}(),
// We dont know which cluster gets how many, but the resultant total should match
},
"Decreasing replicas on fed object decreases the same on clusters": {
// Existing total of local min, max = 2+2, 8+8 decreasing to below
fedHpa: newHpaWithReplicas(NewInt32(3), NewInt32(70), 8),
localHpas: func() map[string]pkgruntime.Object {
// does not matter if scaleability is true
hpas := make(map[string]pkgruntime.Object)
hpas["c1"] = newHpaWithReplicas(NewInt32(2), NewInt32(70), 8)
hpas["c2"] = newHpaWithReplicas(NewInt32(2), NewInt32(70), 8)
return hpas
}(),
// We dont know which cluster gets how many, but the resultant total should match
},
}
adapter := &HpaAdapter{
scaleForbiddenWindow: ScaleForbiddenWindow,
}
for testName, testCase := range testCases {
t.Run(testName, func(t *testing.T) {
if testCase.fedHpa == nil {
testCase.fedHpa = defaultFedHpa
}
scheduledState := adapter.getHpaScheduleState(testCase.fedHpa, testCase.localHpas)
checkClusterConditions(t, testCase.fedHpa, scheduledState)
if testCase.expectedReplicas != nil {
for cluster, replicas := range testCase.expectedReplicas {
scheduledReplicas := scheduledState[cluster]
assert.Equal(t, replicas.min, scheduledReplicas.min)
assert.Equal(t, replicas.max, scheduledReplicas.max)
}
}
})
}
}
func updateHpaStatus(hpa *autoscalingv1.HorizontalPodAutoscaler, currentUtilisation *int32, current, desired int32, scaleable bool) *autoscalingv1.HorizontalPodAutoscaler {
hpa.Status.CurrentReplicas = current
hpa.Status.DesiredReplicas = desired
hpa.Status.CurrentCPUUtilizationPercentage = currentUtilisation
now := metav1.Now()
scaledTime := now
if scaleable {
// definitely more then ScaleForbiddenWindow time ago
scaledTime = metav1.NewTime(now.Time.Add(-2 * ScaleForbiddenWindow))
}
hpa.Status.LastScaleTime = &scaledTime
return hpa
}
func checkClusterConditions(t *testing.T, fedHpa *autoscalingv1.HorizontalPodAutoscaler, scheduled map[string]*replicaNums) {
minTotal := int32(0)
maxTotal := int32(0)
for _, replicas := range scheduled {
minTotal += replicas.min
maxTotal += replicas.max
}
// - Total of max matches the fed max
assert.Equal(t, fedHpa.Spec.MaxReplicas, maxTotal)
// - Total of min is not less then fed min
assert.Condition(t, func() bool {
if *fedHpa.Spec.MinReplicas <= minTotal {
return true
}
return false
})
}
func newHpaWithReplicas(min, targetUtilisation *int32, max int32) *autoscalingv1.HorizontalPodAutoscaler {
return &autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "myhpa",
Namespace: apiv1.NamespaceDefault,
SelfLink: "/api/mylink",
},
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
Kind: "HorizontalPodAutoscaler",
Name: "target-",
},
MinReplicas: min,
MaxReplicas: max,
TargetCPUUtilizationPercentage: targetUtilisation,
},
}
}

View file

@ -0,0 +1,215 @@
/*
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 federatedtypes
import (
"fmt"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
pkgruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/dynamic"
kubeclientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/record"
federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset"
"k8s.io/kubernetes/federation/pkg/federation-controller/util"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/controller/namespace/deletion"
"github.com/golang/glog"
)
const (
NamespaceKind = "namespace"
NamespaceControllerName = "namespaces"
)
func init() {
RegisterFederatedType(NamespaceKind, NamespaceControllerName, []schema.GroupVersionResource{apiv1.SchemeGroupVersion.WithResource(NamespaceControllerName)}, NewNamespaceAdapter)
}
type NamespaceAdapter struct {
client federationclientset.Interface
deleter deletion.NamespacedResourcesDeleterInterface
}
func NewNamespaceAdapter(client federationclientset.Interface, config *restclient.Config, adapterSpecificArgs map[string]interface{}) FederatedTypeAdapter {
dynamicClientPool := dynamic.NewDynamicClientPool(config)
discoverResourcesFunc := client.Discovery().ServerPreferredNamespacedResources
deleter := deletion.NewNamespacedResourcesDeleter(
client.Core().Namespaces(),
dynamicClientPool,
nil,
discoverResourcesFunc,
apiv1.FinalizerKubernetes,
false)
return &NamespaceAdapter{client: client, deleter: deleter}
}
func (a *NamespaceAdapter) Kind() string {
return NamespaceKind
}
func (a *NamespaceAdapter) ObjectType() pkgruntime.Object {
return &apiv1.Namespace{}
}
func (a *NamespaceAdapter) IsExpectedType(obj interface{}) bool {
_, ok := obj.(*apiv1.Namespace)
return ok
}
func (a *NamespaceAdapter) Copy(obj pkgruntime.Object) pkgruntime.Object {
namespace := obj.(*apiv1.Namespace)
return &apiv1.Namespace{
ObjectMeta: util.DeepCopyRelevantObjectMeta(namespace.ObjectMeta),
Spec: *(util.DeepCopyApiTypeOrPanic(&namespace.Spec).(*apiv1.NamespaceSpec)),
}
}
func (a *NamespaceAdapter) Equivalent(obj1, obj2 pkgruntime.Object) bool {
return util.ObjectMetaAndSpecEquivalent(obj1, obj2)
}
func (a *NamespaceAdapter) QualifiedName(obj pkgruntime.Object) QualifiedName {
namespace := obj.(*apiv1.Namespace)
return QualifiedName{Name: namespace.Name}
}
func (a *NamespaceAdapter) ObjectMeta(obj pkgruntime.Object) *metav1.ObjectMeta {
return &obj.(*apiv1.Namespace).ObjectMeta
}
func (a *NamespaceAdapter) FedCreate(obj pkgruntime.Object) (pkgruntime.Object, error) {
namespace := obj.(*apiv1.Namespace)
return a.client.CoreV1().Namespaces().Create(namespace)
}
func (a *NamespaceAdapter) FedDelete(qualifiedName QualifiedName, options *metav1.DeleteOptions) error {
return a.client.CoreV1().Namespaces().Delete(qualifiedName.Name, options)
}
func (a *NamespaceAdapter) FedGet(qualifiedName QualifiedName) (pkgruntime.Object, error) {
return a.client.CoreV1().Namespaces().Get(qualifiedName.Name, metav1.GetOptions{})
}
func (a *NamespaceAdapter) FedList(namespace string, options metav1.ListOptions) (pkgruntime.Object, error) {
return a.client.CoreV1().Namespaces().List(options)
}
func (a *NamespaceAdapter) FedUpdate(obj pkgruntime.Object) (pkgruntime.Object, error) {
namespace := obj.(*apiv1.Namespace)
return a.client.CoreV1().Namespaces().Update(namespace)
}
func (a *NamespaceAdapter) FedWatch(namespace string, options metav1.ListOptions) (watch.Interface, error) {
return a.client.CoreV1().Namespaces().Watch(options)
}
func (a *NamespaceAdapter) ClusterCreate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) {
namespace := obj.(*apiv1.Namespace)
return client.CoreV1().Namespaces().Create(namespace)
}
func (a *NamespaceAdapter) ClusterDelete(client kubeclientset.Interface, qualifiedName QualifiedName, options *metav1.DeleteOptions) error {
return client.CoreV1().Namespaces().Delete(qualifiedName.Name, options)
}
func (a *NamespaceAdapter) ClusterGet(client kubeclientset.Interface, qualifiedName QualifiedName) (pkgruntime.Object, error) {
return client.CoreV1().Namespaces().Get(qualifiedName.Name, metav1.GetOptions{})
}
func (a *NamespaceAdapter) ClusterList(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (pkgruntime.Object, error) {
return client.CoreV1().Namespaces().List(options)
}
func (a *NamespaceAdapter) ClusterUpdate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) {
namespace := obj.(*apiv1.Namespace)
return client.CoreV1().Namespaces().Update(namespace)
}
func (a *NamespaceAdapter) ClusterWatch(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (watch.Interface, error) {
return client.CoreV1().Namespaces().Watch(options)
}
func (a *NamespaceAdapter) IsSchedulingAdapter() bool {
return false
}
func (a *NamespaceAdapter) NewTestObject(namespace string) pkgruntime.Object {
return &apiv1.Namespace{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-namespace-",
},
Spec: apiv1.NamespaceSpec{
Finalizers: []apiv1.FinalizerName{apiv1.FinalizerKubernetes},
},
}
}
// CleanUpNamespace deletes all resources in a given namespace.
func (a *NamespaceAdapter) CleanUpNamespace(obj pkgruntime.Object, eventRecorder record.EventRecorder) (pkgruntime.Object, error) {
namespace := obj.(*apiv1.Namespace)
name := namespace.Name
// Set Terminating status.
updatedNamespace := &apiv1.Namespace{
ObjectMeta: namespace.ObjectMeta,
Spec: namespace.Spec,
Status: apiv1.NamespaceStatus{
Phase: apiv1.NamespaceTerminating,
},
}
var err error
if namespace.Status.Phase != apiv1.NamespaceTerminating {
glog.V(2).Infof("Marking ns %s as terminating", name)
eventRecorder.Event(namespace, api.EventTypeNormal, "DeleteNamespace", fmt.Sprintf("Marking for deletion"))
_, err = a.FedUpdate(updatedNamespace)
if err != nil {
return nil, fmt.Errorf("failed to update namespace: %v", err)
}
}
if hasFinalizerInSpec(updatedNamespace, apiv1.FinalizerKubernetes) {
// Delete resources in this namespace.
err = a.deleter.Delete(name)
if err != nil {
return nil, fmt.Errorf("error in deleting resources in namespace %s: %v", name, err)
}
glog.V(2).Infof("Removed kubernetes finalizer from ns %s", name)
// Fetch the updated Namespace.
obj, err = a.FedGet(QualifiedName{Name: name})
updatedNamespace = obj.(*apiv1.Namespace)
if err != nil {
return nil, fmt.Errorf("error in fetching updated namespace %s: %s", name, err)
}
}
return updatedNamespace, nil
}
func hasFinalizerInSpec(namespace *apiv1.Namespace, finalizer apiv1.FinalizerName) bool {
for i := range namespace.Spec.Finalizers {
if namespace.Spec.Finalizers[i] == finalizer {
return true
}
}
return false
}

View file

@ -0,0 +1,41 @@
/*
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 federatedtypes
import (
"fmt"
)
// QualifiedName comprises a resource name with an optional namespace.
// If namespace is provided, a QualifiedName will be rendered as
// "<namespace>/<name>". If not, it will be rendered as "name". This
// is intended to allow the FederatedTypeAdapter interface and its
// consumers to operate on both namespaces and namespace-qualified
// resources.
type QualifiedName struct {
Namespace string
Name string
}
// String returns the general purpose string representation
func (n QualifiedName) String() string {
if len(n.Namespace) == 0 {
return n.Name
}
return fmt.Sprintf("%s/%s", n.Namespace, n.Name)
}

View file

@ -0,0 +1,59 @@
/*
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 federatedtypes
import (
"fmt"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// FederatedType configures federation for a kubernetes type
type FederatedType struct {
Kind string
ControllerName string
RequiredResources []schema.GroupVersionResource
AdapterFactory AdapterFactory
}
var typeRegistry = make(map[string]FederatedType)
// RegisterFederatedType ensures that configuration for the given kind will be returned by the FederatedTypes method.
func RegisterFederatedType(kind, controllerName string, requiredResources []schema.GroupVersionResource, factory AdapterFactory) {
_, ok := typeRegistry[kind]
if ok {
// TODO Is panicking ok given that this is part of a type-registration mechanism
panic(fmt.Sprintf("Federated type %q has already been registered", kind))
}
typeRegistry[kind] = FederatedType{
Kind: kind,
ControllerName: controllerName,
RequiredResources: requiredResources,
AdapterFactory: factory,
}
}
// FederatedTypes returns a mapping of kind (e.g. "secret") to the
// type information required to configure its federation.
func FederatedTypes() map[string]FederatedType {
// TODO copy RequiredResources to avoid accidental mutation
result := make(map[string]FederatedType)
for key, value := range typeRegistry {
result[key] = value
}
return result
}

View file

@ -0,0 +1,189 @@
/*
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 federatedtypes
import (
apiv1 "k8s.io/api/core/v1"
extensionsv1 "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
pkgruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
kubeclientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset"
fedutil "k8s.io/kubernetes/federation/pkg/federation-controller/util"
)
const (
ReplicaSetKind = "replicaset"
ReplicaSetControllerName = "replicasets"
FedReplicaSetPreferencesAnnotation = "federation.kubernetes.io/replica-set-preferences"
)
func init() {
RegisterFederatedType(ReplicaSetKind, ReplicaSetControllerName, []schema.GroupVersionResource{extensionsv1.SchemeGroupVersion.WithResource(ReplicaSetControllerName)}, NewReplicaSetAdapter)
}
type ReplicaSetAdapter struct {
*replicaSchedulingAdapter
client federationclientset.Interface
}
func NewReplicaSetAdapter(client federationclientset.Interface, config *restclient.Config, adapterSpecificArgs map[string]interface{}) FederatedTypeAdapter {
replicaSchedulingAdapter := replicaSchedulingAdapter{
preferencesAnnotationName: FedReplicaSetPreferencesAnnotation,
updateStatusFunc: func(obj pkgruntime.Object, schedulingInfo interface{}) error {
rs := obj.(*extensionsv1.ReplicaSet)
typedStatus := schedulingInfo.(*ReplicaSchedulingInfo).Status
if typedStatus.Replicas != rs.Status.Replicas || typedStatus.FullyLabeledReplicas != rs.Status.FullyLabeledReplicas ||
typedStatus.ReadyReplicas != rs.Status.ReadyReplicas || typedStatus.AvailableReplicas != rs.Status.AvailableReplicas {
rs.Status = extensionsv1.ReplicaSetStatus{
Replicas: typedStatus.Replicas,
FullyLabeledReplicas: typedStatus.Replicas,
ReadyReplicas: typedStatus.ReadyReplicas,
AvailableReplicas: typedStatus.AvailableReplicas,
}
_, err := client.Extensions().ReplicaSets(rs.Namespace).UpdateStatus(rs)
return err
}
return nil
},
}
return &ReplicaSetAdapter{&replicaSchedulingAdapter, client}
}
func (a *ReplicaSetAdapter) Kind() string {
return ReplicaSetKind
}
func (a *ReplicaSetAdapter) ObjectType() pkgruntime.Object {
return &extensionsv1.ReplicaSet{}
}
func (a *ReplicaSetAdapter) IsExpectedType(obj interface{}) bool {
_, ok := obj.(*extensionsv1.ReplicaSet)
return ok
}
func (a *ReplicaSetAdapter) Copy(obj pkgruntime.Object) pkgruntime.Object {
rs := obj.(*extensionsv1.ReplicaSet)
return &extensionsv1.ReplicaSet{
ObjectMeta: fedutil.DeepCopyRelevantObjectMeta(rs.ObjectMeta),
Spec: *fedutil.DeepCopyApiTypeOrPanic(&rs.Spec).(*extensionsv1.ReplicaSetSpec),
}
}
func (a *ReplicaSetAdapter) Equivalent(obj1, obj2 pkgruntime.Object) bool {
return fedutil.ObjectMetaAndSpecEquivalent(obj1, obj2)
}
func (a *ReplicaSetAdapter) QualifiedName(obj pkgruntime.Object) QualifiedName {
replicaset := obj.(*extensionsv1.ReplicaSet)
return QualifiedName{Namespace: replicaset.Namespace, Name: replicaset.Name}
}
func (a *ReplicaSetAdapter) ObjectMeta(obj pkgruntime.Object) *metav1.ObjectMeta {
return &obj.(*extensionsv1.ReplicaSet).ObjectMeta
}
func (a *ReplicaSetAdapter) FedCreate(obj pkgruntime.Object) (pkgruntime.Object, error) {
replicaset := obj.(*extensionsv1.ReplicaSet)
return a.client.Extensions().ReplicaSets(replicaset.Namespace).Create(replicaset)
}
func (a *ReplicaSetAdapter) FedDelete(qualifiedName QualifiedName, options *metav1.DeleteOptions) error {
return a.client.Extensions().ReplicaSets(qualifiedName.Namespace).Delete(qualifiedName.Name, options)
}
func (a *ReplicaSetAdapter) FedGet(qualifiedName QualifiedName) (pkgruntime.Object, error) {
return a.client.Extensions().ReplicaSets(qualifiedName.Namespace).Get(qualifiedName.Name, metav1.GetOptions{})
}
func (a *ReplicaSetAdapter) FedList(namespace string, options metav1.ListOptions) (pkgruntime.Object, error) {
return a.client.Extensions().ReplicaSets(namespace).List(options)
}
func (a *ReplicaSetAdapter) FedUpdate(obj pkgruntime.Object) (pkgruntime.Object, error) {
replicaset := obj.(*extensionsv1.ReplicaSet)
return a.client.Extensions().ReplicaSets(replicaset.Namespace).Update(replicaset)
}
func (a *ReplicaSetAdapter) FedWatch(namespace string, options metav1.ListOptions) (watch.Interface, error) {
return a.client.Extensions().ReplicaSets(namespace).Watch(options)
}
func (a *ReplicaSetAdapter) ClusterCreate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) {
replicaset := obj.(*extensionsv1.ReplicaSet)
return client.Extensions().ReplicaSets(replicaset.Namespace).Create(replicaset)
}
func (a *ReplicaSetAdapter) ClusterDelete(client kubeclientset.Interface, qualifiedName QualifiedName, options *metav1.DeleteOptions) error {
return client.Extensions().ReplicaSets(qualifiedName.Namespace).Delete(qualifiedName.Name, options)
}
func (a *ReplicaSetAdapter) ClusterGet(client kubeclientset.Interface, qualifiedName QualifiedName) (pkgruntime.Object, error) {
return client.Extensions().ReplicaSets(qualifiedName.Namespace).Get(qualifiedName.Name, metav1.GetOptions{})
}
func (a *ReplicaSetAdapter) ClusterList(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (pkgruntime.Object, error) {
return client.Extensions().ReplicaSets(namespace).List(options)
}
func (a *ReplicaSetAdapter) ClusterUpdate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) {
replicaset := obj.(*extensionsv1.ReplicaSet)
return client.Extensions().ReplicaSets(replicaset.Namespace).Update(replicaset)
}
func (a *ReplicaSetAdapter) ClusterWatch(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (watch.Interface, error) {
return client.Extensions().ReplicaSets(namespace).Watch(options)
}
func (a *ReplicaSetAdapter) EquivalentIgnoringSchedule(obj1, obj2 pkgruntime.Object) bool {
replicaset1 := obj1.(*extensionsv1.ReplicaSet)
replicaset2 := a.Copy(obj2).(*extensionsv1.ReplicaSet)
replicaset2.Spec.Replicas = replicaset1.Spec.Replicas
return fedutil.ObjectMetaAndSpecEquivalent(replicaset1, replicaset2)
}
func (a *ReplicaSetAdapter) NewTestObject(namespace string) pkgruntime.Object {
replicas := int32(3)
zero := int64(0)
return &extensionsv1.ReplicaSet{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-replicaset-",
Namespace: namespace,
},
Spec: extensionsv1.ReplicaSetSpec{
Replicas: &replicas,
Template: apiv1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"foo": "bar"},
},
Spec: apiv1.PodSpec{
TerminationGracePeriodSeconds: &zero,
Containers: []apiv1.Container{
{
Name: "nginx",
Image: "nginx",
},
},
},
},
},
}
}

View file

@ -0,0 +1,356 @@
/*
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 federatedtypes
import (
"bytes"
"fmt"
"reflect"
"sort"
"time"
apiv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
pkgruntime "k8s.io/apimachinery/pkg/runtime"
fedapi "k8s.io/kubernetes/federation/apis/federation"
federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1"
fedutil "k8s.io/kubernetes/federation/pkg/federation-controller/util"
hpautil "k8s.io/kubernetes/federation/pkg/federation-controller/util/hpa"
"k8s.io/kubernetes/federation/pkg/federation-controller/util/planner"
"k8s.io/kubernetes/federation/pkg/federation-controller/util/podanalyzer"
"k8s.io/kubernetes/federation/pkg/federation-controller/util/replicapreferences"
"github.com/golang/glog"
)
// ScheduleAction is used by the interface ScheduleObject of SchedulingAdapter
// to sync controller reconcile to convey the action type needed for the
// particular cluster local object in ScheduleObject
type ScheduleAction string
const (
ActionAdd = "add"
ActionDelete = "delete"
)
// ReplicaStatus contains the details of status fields from the cluster objects,
// which need accumulation to update the status of the federated object.
type ReplicaStatus struct {
Replicas int32
UpdatedReplicas int32
FullyLabeledReplicas int32
ReadyReplicas int32
AvailableReplicas int32
}
// ReplicaScheduleState is the result of adapter specific schedule() function,
// which is then used to update objects into clusters.
type ReplicaScheduleState struct {
isSelected bool
replicas int64
}
// ReplicaSchedulingInfo wraps the information that a replica type (rs or deployment)
// SchedulingAdapter needs to update objects per a schedule.
type ReplicaSchedulingInfo struct {
ScheduleState map[string]*ReplicaScheduleState
Status ReplicaStatus
}
// SchedulingAdapter defines operations for interacting with a
// federated type that requires more complex synchronization logic.
type SchedulingAdapter interface {
GetSchedule(obj pkgruntime.Object, key string, clusters []*federationapi.Cluster, informer fedutil.FederatedInformer) (interface{}, error)
ScheduleObject(cluster *federationapi.Cluster, clusterObj pkgruntime.Object, federationObjCopy pkgruntime.Object, schedulingInfo interface{}) (pkgruntime.Object, ScheduleAction, error)
UpdateFederatedStatus(obj pkgruntime.Object, schedulingInfo interface{}) error
// EquivalentIgnoringSchedule returns whether obj1 and obj2 are
// equivalent ignoring differences due to scheduling.
EquivalentIgnoringSchedule(obj1, obj2 pkgruntime.Object) bool
}
// replicaSchedulingAdapter is meant to be embedded in other type adapters that require
// workload scheduling with actual pod replicas.
type replicaSchedulingAdapter struct {
preferencesAnnotationName string
updateStatusFunc func(pkgruntime.Object, interface{}) error
}
func (a *replicaSchedulingAdapter) IsSchedulingAdapter() bool {
return true
}
func isSelected(names []string, name string) bool {
for _, val := range names {
if val == name {
return true
}
}
return false
}
func isObjHpaControlled(fedObj pkgruntime.Object) (bool, error) {
hpaSelectedClusters, error := hpautil.GetHpaTargetClusterList(fedObj)
if error != nil {
return false, error
}
if hpaSelectedClusters == nil {
return false, nil
}
return true, nil
}
// initializeScheduleState initializes the schedule state for consumption by schedule
// functions (schedule or simple schedule). After this initialization the state would
// already have information, if only a subset of clusters targetted by hpa, or all clusters
// need to be considered by the actual scheduling functions.
// The return bool named hpaControlled tells if this object is controlled by hpa or not.
func initializeScheduleState(fedObj pkgruntime.Object, clusterNames []string) (map[string]*ReplicaScheduleState, bool, error) {
initialState := make(map[string]*ReplicaScheduleState)
hpaControlled := false
hpaSelectedClusters, error := hpautil.GetHpaTargetClusterList(fedObj)
if error != nil {
return nil, hpaControlled, error
}
if hpaSelectedClusters != nil {
hpaControlled = true
}
for _, clusterName := range clusterNames {
replicaState := ReplicaScheduleState{
isSelected: false,
replicas: 0,
}
if hpaControlled {
if isSelected(hpaSelectedClusters.Names, clusterName) {
replicaState.isSelected = true
}
}
initialState[clusterName] = &replicaState
}
return initialState, hpaControlled, nil
}
func (a *replicaSchedulingAdapter) GetSchedule(obj pkgruntime.Object, key string, clusters []*federationapi.Cluster, informer fedutil.FederatedInformer) (interface{}, error) {
var clusterNames []string
for _, cluster := range clusters {
clusterNames = append(clusterNames, cluster.Name)
}
// Schedule the pods across the existing clusters.
objectGetter := func(clusterName, key string) (interface{}, bool, error) {
return informer.GetTargetStore().GetByKey(clusterName, key)
}
podsGetter := func(clusterName string, obj pkgruntime.Object) (*apiv1.PodList, error) {
clientset, err := informer.GetClientsetForCluster(clusterName)
if err != nil {
return nil, err
}
selectorObj := reflect.ValueOf(obj).Elem().FieldByName("Spec").FieldByName("Selector").Interface().(*metav1.LabelSelector)
selector, err := metav1.LabelSelectorAsSelector(selectorObj)
if err != nil {
return nil, fmt.Errorf("invalid selector: %v", err)
}
metadata, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
return clientset.Core().Pods(metadata.GetNamespace()).List(metav1.ListOptions{LabelSelector: selector.String()})
}
initializedState, hpaControlled, err := initializeScheduleState(obj, clusterNames)
if err != nil {
return nil, err
}
if hpaControlled {
state, err := simpleSchedule(initializedState, key, objectGetter)
if err != nil {
return nil, err
}
return &ReplicaSchedulingInfo{
ScheduleState: state,
Status: ReplicaStatus{},
}, nil
}
currentReplicasPerCluster, estimatedCapacity, err := clustersReplicaState(clusterNames, key, objectGetter, podsGetter)
if err != nil {
return nil, err
}
fedPref, err := replicapreferences.GetAllocationPreferences(obj, a.preferencesAnnotationName)
if err != nil {
glog.Infof("Invalid workload-type specific preference, using default. object: %v, err: %v", obj, err)
}
if fedPref == nil {
fedPref = &fedapi.ReplicaAllocationPreferences{
Clusters: map[string]fedapi.ClusterPreferences{
"*": {Weight: 1},
},
}
}
plnr := planner.NewPlanner(fedPref)
return &ReplicaSchedulingInfo{
ScheduleState: schedule(plnr, obj, key, clusterNames, currentReplicasPerCluster, estimatedCapacity, initializedState),
Status: ReplicaStatus{},
}, nil
}
func (a *replicaSchedulingAdapter) ScheduleObject(cluster *federationapi.Cluster, clusterObj pkgruntime.Object, federationObjCopy pkgruntime.Object, schedulingInfo interface{}) (pkgruntime.Object, ScheduleAction, error) {
typedSchedulingInfo := schedulingInfo.(*ReplicaSchedulingInfo)
clusterScheduleState := typedSchedulingInfo.ScheduleState[cluster.Name]
if clusterObj != nil {
schedulingStatusVal := reflect.ValueOf(typedSchedulingInfo).Elem().FieldByName("Status")
objStatusVal := reflect.ValueOf(clusterObj).Elem().FieldByName("Status")
for i := 0; i < schedulingStatusVal.NumField(); i++ {
schedulingStatusField := schedulingStatusVal.Field(i)
schedulingStatusFieldName := schedulingStatusVal.Type().Field(i).Name
objStatusField := objStatusVal.FieldByName(schedulingStatusFieldName)
if objStatusField.IsValid() {
current := schedulingStatusField.Int()
additional := objStatusField.Int()
schedulingStatusField.SetInt(current + additional)
}
}
}
var action ScheduleAction = ""
specReplicas := int32(0)
// If the cluster has been selected (isSelected = true; for example by hpa)
// and the obj does not get any replicas, then it should create one with
// 0 replicas (which can then be scaled by hpa in that cluster).
// On the other hand we keep the action as "unassigned" if this cluster was
// not selected, and let the sync controller decide what to do.
if clusterScheduleState.isSelected {
specReplicas = int32(clusterScheduleState.replicas)
action = ActionAdd
}
reflect.ValueOf(federationObjCopy).Elem().FieldByName("Spec").FieldByName("Replicas").Set(reflect.ValueOf(&specReplicas))
return federationObjCopy, action, nil
}
func (a *replicaSchedulingAdapter) UpdateFederatedStatus(obj pkgruntime.Object, schedulingInfo interface{}) error {
return a.updateStatusFunc(obj, schedulingInfo)
}
// simpleSchedule get replicas from only those clusters which are selected (by hpa scheduler).
// This aim of this is to ensure that this controller does not update objects, which are
// targetted by hpa.
func simpleSchedule(scheduleState map[string]*ReplicaScheduleState, key string, objectGetter func(clusterName string, key string) (interface{}, bool, error)) (map[string]*ReplicaScheduleState, error) {
for clusterName, state := range scheduleState {
// Get and consider replicas only for those clusters which are selected by hpa.
if state.isSelected {
obj, exists, err := objectGetter(clusterName, key)
if err != nil {
return nil, err
}
if !exists {
continue
}
state.replicas = reflect.ValueOf(obj).Elem().FieldByName("Spec").FieldByName("Replicas").Elem().Int()
}
}
return scheduleState, nil
}
func schedule(planner *planner.Planner, obj pkgruntime.Object, key string, clusterNames []string, currentReplicasPerCluster map[string]int64, estimatedCapacity map[string]int64, initialState map[string]*ReplicaScheduleState) map[string]*ReplicaScheduleState {
// TODO: integrate real scheduler
replicas := reflect.ValueOf(obj).Elem().FieldByName("Spec").FieldByName("Replicas").Elem().Int()
scheduleResult, overflow := planner.Plan(replicas, clusterNames, currentReplicasPerCluster, estimatedCapacity, key)
// Ensure that all current clusters end up in the scheduling result.
// initialState, is preinitialized with all isSelected to false.
result := initialState
for clusterName := range currentReplicasPerCluster {
// We consider 0 replicas equaling to no need of creating a new object.
// isSchedule remains false in such case.
result[clusterName].replicas = 0
}
for clusterName, replicas := range scheduleResult {
result[clusterName].isSelected = true
result[clusterName].replicas = replicas
}
for clusterName, replicas := range overflow {
result[clusterName].isSelected = true
result[clusterName].replicas += replicas
}
if glog.V(4) {
buf := bytes.NewBufferString(fmt.Sprintf("Schedule - %q\n", key))
sort.Strings(clusterNames)
for _, clusterName := range clusterNames {
cur := currentReplicasPerCluster[clusterName]
target := scheduleResult[clusterName]
fmt.Fprintf(buf, "%s: current: %d target: %d", clusterName, cur, target)
if over, found := overflow[clusterName]; found {
fmt.Fprintf(buf, " overflow: %d", over)
}
if capacity, found := estimatedCapacity[clusterName]; found {
fmt.Fprintf(buf, " capacity: %d", capacity)
}
fmt.Fprintf(buf, "\n")
}
glog.V(4).Infof(buf.String())
}
return result
}
// clusterReplicaState returns information about the scheduling state of the pods running in the federated clusters.
func clustersReplicaState(
clusterNames []string,
key string,
objectGetter func(clusterName string, key string) (interface{}, bool, error),
podsGetter func(clusterName string, obj pkgruntime.Object) (*apiv1.PodList, error)) (currentReplicasPerCluster map[string]int64, estimatedCapacity map[string]int64, err error) {
currentReplicasPerCluster = make(map[string]int64)
estimatedCapacity = make(map[string]int64)
for _, clusterName := range clusterNames {
obj, exists, err := objectGetter(clusterName, key)
if err != nil {
return nil, nil, err
}
if !exists {
continue
}
replicas := reflect.ValueOf(obj).Elem().FieldByName("Spec").FieldByName("Replicas").Elem().Int()
readyReplicas := reflect.ValueOf(obj).Elem().FieldByName("Status").FieldByName("ReadyReplicas").Int()
if replicas == readyReplicas {
currentReplicasPerCluster[clusterName] = readyReplicas
} else {
pods, err := podsGetter(clusterName, obj.(pkgruntime.Object))
if err != nil {
return nil, nil, err
}
podStatus := podanalyzer.AnalyzePods(pods, time.Now())
currentReplicasPerCluster[clusterName] = int64(podStatus.RunningAndReady) // include pending as well?
unschedulable := int64(podStatus.Unschedulable)
if unschedulable > 0 {
estimatedCapacity[clusterName] = replicas - unschedulable
}
}
}
return currentReplicasPerCluster, estimatedCapacity, nil
}

View file

@ -0,0 +1,163 @@
/*
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 federatedtypes
import (
"fmt"
"testing"
"time"
apiv1 "k8s.io/api/core/v1"
extensionsv1 "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
pkgruntime "k8s.io/apimachinery/pkg/runtime"
"github.com/stretchr/testify/assert"
)
func TestClusterReplicaState(t *testing.T) {
uncalledPodsGetter := func(clusterName string, obj pkgruntime.Object) (*apiv1.PodList, error) {
t.Fatal("podsGetter should not be called when workload objects are all ready.")
return nil, nil
}
podsByReplicaSet := make(map[pkgruntime.Object][]*apiv1.Pod)
podsGetter := func(clusterName string, obj pkgruntime.Object) (*apiv1.PodList, error) {
pods, ok := podsByReplicaSet[obj]
if !ok {
t.Fatalf("No pods found in test data for replica set %v", obj)
return nil, fmt.Errorf("Not found")
}
var podListPods []apiv1.Pod
for _, pod := range pods {
podListPods = append(podListPods, *pod)
}
return &apiv1.PodList{Items: podListPods}, nil
}
readyCondition := apiv1.PodCondition{Type: apiv1.PodReady}
unschedulableCondition := apiv1.PodCondition{
Type: apiv1.PodScheduled,
Status: apiv1.ConditionFalse,
Reason: apiv1.PodReasonUnschedulable,
LastTransitionTime: metav1.NewTime(time.Now().Add(-1 * time.Hour)),
}
one := int64(1)
two := int64(2)
tests := map[string]struct {
rs1Replicas int32
rs2Replicas int32
rs1ReadyReplicas int32
rs2ReadyReplicas int32
podsGetter func(clusterName string, obj pkgruntime.Object) (*apiv1.PodList, error)
pod1Phase apiv1.PodPhase
pod1Condition apiv1.PodCondition
pod2Phase apiv1.PodPhase
pod2Condition apiv1.PodCondition
cluster1Replicas *int64
cluster2Replicas *int64
cluster1UnschedulableReplicas *int64
cluster2UnschedulableReplicas *int64
}{
"All replica sets have an equal number of requested and ready replicas.": {rs1Replicas: 2, rs2Replicas: 2, rs1ReadyReplicas: 2, rs2ReadyReplicas: 2, podsGetter: uncalledPodsGetter, cluster1Replicas: &two, cluster2Replicas: &two},
"One replica set has a pending schedulable pod": {rs1Replicas: 2, rs2Replicas: 2, rs1ReadyReplicas: 1, rs2ReadyReplicas: 2, podsGetter: podsGetter, pod1Phase: apiv1.PodRunning, pod1Condition: readyCondition, pod2Phase: apiv1.PodPending, cluster1Replicas: &one, cluster2Replicas: &two},
"One replica set has an unschedulable pod": {rs1Replicas: 2, rs2Replicas: 2, rs1ReadyReplicas: 1, rs2ReadyReplicas: 2, podsGetter: podsGetter, pod1Phase: apiv1.PodRunning, pod1Condition: readyCondition, pod2Phase: apiv1.PodPending, pod2Condition: unschedulableCondition, cluster1Replicas: &one, cluster2Replicas: &two, cluster1UnschedulableReplicas: &one},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
clusters := []string{"one", "two"}
replicaSetsByCluster := make(map[string]*extensionsv1.ReplicaSet)
replicaSetGetter := func(clusterName string, key string) (interface{}, bool, error) {
rs, ok := replicaSetsByCluster[clusterName]
if !ok {
t.Fatalf("No replica set found in test data for %v", clusterName)
return nil, false, fmt.Errorf("Not found")
}
return rs, true, nil
}
rs1 := newReplicaSetWithReplicas("one", tt.rs1Replicas)
rs2 := newReplicaSetWithReplicas("two", tt.rs2Replicas)
rs1.Spec.Replicas = &tt.rs1Replicas
rs2.Spec.Replicas = &tt.rs2Replicas
rs1.Status.ReadyReplicas = tt.rs1ReadyReplicas
rs2.Status.ReadyReplicas = tt.rs2ReadyReplicas
replicaSetsByCluster["one"] = rs1
replicaSetsByCluster["two"] = rs2
pod1 := newPod("one")
pod2 := newPod("two")
podThree := newPod("three")
podFour := newPod("four")
pod1.Status.Phase = tt.pod1Phase
pod2.Status.Phase = tt.pod2Phase
pod1.Status.Conditions = []apiv1.PodCondition{tt.pod1Condition}
pod2.Status.Conditions = []apiv1.PodCondition{tt.pod2Condition}
podsByReplicaSet[rs1] = []*apiv1.Pod{pod1, pod2}
podsByReplicaSet[rs2] = []*apiv1.Pod{podThree, podFour}
current, estimatedCapacity, err := clustersReplicaState(clusters, "", replicaSetGetter, tt.podsGetter)
assert.Nil(t, err)
wantedCurrent := make(map[string]int64)
if tt.cluster1Replicas != nil {
wantedCurrent["one"] = *tt.cluster1Replicas
}
if tt.cluster2Replicas != nil {
wantedCurrent["two"] = *tt.cluster2Replicas
}
assert.Equal(t, wantedCurrent, current)
wantedEstimatedCapacity := make(map[string]int64)
if tt.cluster1UnschedulableReplicas != nil {
wantedEstimatedCapacity["one"] = *tt.cluster1UnschedulableReplicas
}
if tt.cluster2UnschedulableReplicas != nil {
wantedEstimatedCapacity["two"] = *tt.cluster2UnschedulableReplicas
}
assert.Equal(t, wantedEstimatedCapacity, estimatedCapacity)
})
}
}
func newReplicaSetWithReplicas(name string, replicas int32) *extensionsv1.ReplicaSet {
return &extensionsv1.ReplicaSet{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: metav1.NamespaceDefault,
SelfLink: "/api/v1/namespaces/default/replicasets/name",
},
Spec: extensionsv1.ReplicaSetSpec{
Replicas: &replicas,
},
}
}
func newPod(name string) *apiv1.Pod {
return &apiv1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: metav1.NamespaceDefault,
},
}
}

View file

@ -0,0 +1,152 @@
/*
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 federatedtypes
import (
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
pkgruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
kubeclientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset"
"k8s.io/kubernetes/federation/pkg/federation-controller/util"
)
const (
SecretKind = "secret"
SecretControllerName = "secrets"
)
func init() {
RegisterFederatedType(SecretKind, SecretControllerName, []schema.GroupVersionResource{apiv1.SchemeGroupVersion.WithResource(SecretControllerName)}, NewSecretAdapter)
}
type SecretAdapter struct {
client federationclientset.Interface
}
func NewSecretAdapter(client federationclientset.Interface, config *restclient.Config, adapterSpecificArgs map[string]interface{}) FederatedTypeAdapter {
return &SecretAdapter{client: client}
}
func (a *SecretAdapter) Kind() string {
return SecretKind
}
func (a *SecretAdapter) ObjectType() pkgruntime.Object {
return &apiv1.Secret{}
}
func (a *SecretAdapter) IsExpectedType(obj interface{}) bool {
_, ok := obj.(*apiv1.Secret)
return ok
}
func (a *SecretAdapter) Copy(obj pkgruntime.Object) pkgruntime.Object {
secret := obj.(*apiv1.Secret)
return &apiv1.Secret{
ObjectMeta: util.DeepCopyRelevantObjectMeta(secret.ObjectMeta),
Data: secret.Data,
Type: secret.Type,
}
}
func (a *SecretAdapter) Equivalent(obj1, obj2 pkgruntime.Object) bool {
secret1 := obj1.(*apiv1.Secret)
secret2 := obj2.(*apiv1.Secret)
return util.SecretEquivalent(*secret1, *secret2)
}
func (a *SecretAdapter) QualifiedName(obj pkgruntime.Object) QualifiedName {
secret := obj.(*apiv1.Secret)
return QualifiedName{Namespace: secret.Namespace, Name: secret.Name}
}
func (a *SecretAdapter) ObjectMeta(obj pkgruntime.Object) *metav1.ObjectMeta {
return &obj.(*apiv1.Secret).ObjectMeta
}
func (a *SecretAdapter) FedCreate(obj pkgruntime.Object) (pkgruntime.Object, error) {
secret := obj.(*apiv1.Secret)
return a.client.CoreV1().Secrets(secret.Namespace).Create(secret)
}
func (a *SecretAdapter) FedDelete(qualifiedName QualifiedName, options *metav1.DeleteOptions) error {
return a.client.CoreV1().Secrets(qualifiedName.Namespace).Delete(qualifiedName.Name, options)
}
func (a *SecretAdapter) FedGet(qualifiedName QualifiedName) (pkgruntime.Object, error) {
return a.client.CoreV1().Secrets(qualifiedName.Namespace).Get(qualifiedName.Name, metav1.GetOptions{})
}
func (a *SecretAdapter) FedList(namespace string, options metav1.ListOptions) (pkgruntime.Object, error) {
return a.client.CoreV1().Secrets(namespace).List(options)
}
func (a *SecretAdapter) FedUpdate(obj pkgruntime.Object) (pkgruntime.Object, error) {
secret := obj.(*apiv1.Secret)
return a.client.CoreV1().Secrets(secret.Namespace).Update(secret)
}
func (a *SecretAdapter) FedWatch(namespace string, options metav1.ListOptions) (watch.Interface, error) {
return a.client.CoreV1().Secrets(namespace).Watch(options)
}
func (a *SecretAdapter) ClusterCreate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) {
secret := obj.(*apiv1.Secret)
return client.CoreV1().Secrets(secret.Namespace).Create(secret)
}
func (a *SecretAdapter) ClusterDelete(client kubeclientset.Interface, qualifiedName QualifiedName, options *metav1.DeleteOptions) error {
return client.CoreV1().Secrets(qualifiedName.Namespace).Delete(qualifiedName.Name, options)
}
func (a *SecretAdapter) ClusterGet(client kubeclientset.Interface, qualifiedName QualifiedName) (pkgruntime.Object, error) {
return client.CoreV1().Secrets(qualifiedName.Namespace).Get(qualifiedName.Name, metav1.GetOptions{})
}
func (a *SecretAdapter) ClusterList(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (pkgruntime.Object, error) {
return client.CoreV1().Secrets(namespace).List(options)
}
func (a *SecretAdapter) ClusterUpdate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) {
secret := obj.(*apiv1.Secret)
return client.CoreV1().Secrets(secret.Namespace).Update(secret)
}
func (a *SecretAdapter) ClusterWatch(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (watch.Interface, error) {
return client.CoreV1().Secrets(namespace).Watch(options)
}
func (a *SecretAdapter) IsSchedulingAdapter() bool {
return false
}
func (a *SecretAdapter) NewTestObject(namespace string) pkgruntime.Object {
return &apiv1.Secret{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-secret-",
Namespace: namespace,
},
Data: map[string][]byte{
"A": []byte("ala ma kota"),
},
Type: apiv1.SecretTypeOpaque,
}
}

View file

@ -0,0 +1,32 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["doc.go"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//federation/pkg/federation-controller/cluster:all-srcs",
"//federation/pkg/federation-controller/ingress:all-srcs",
"//federation/pkg/federation-controller/job:all-srcs",
"//federation/pkg/federation-controller/service:all-srcs",
"//federation/pkg/federation-controller/sync:all-srcs",
"//federation/pkg/federation-controller/util:all-srcs",
],
tags = ["automanaged"],
)

View file

@ -0,0 +1,8 @@
approvers:
- quinton-hoole
- nikhiljindal
- madhusudancs
reviewers:
- quinton-hoole
- nikhiljindal
- madhusudancs

View file

@ -0,0 +1,65 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["clustercontroller_test.go"],
library = ":go_default_library",
deps = [
"//federation/apis/federation/v1beta1:go_default_library",
"//federation/client/clientset_generated/federation_clientset:go_default_library",
"//pkg/api/testapi:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"cluster_client.go",
"clustercontroller.go",
"doc.go",
],
deps = [
"//federation/apis/federation/v1beta1:go_default_library",
"//federation/client/cache:go_default_library",
"//federation/client/clientset_generated/federation_clientset:go_default_library",
"//federation/pkg/federation-controller/util:go_default_library",
"//pkg/api:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/kubelet/apis:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,170 @@
/*
Copyright 2016 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 cluster
import (
"fmt"
"strings"
"github.com/golang/glog"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
restclient "k8s.io/client-go/rest"
federation_v1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1"
"k8s.io/kubernetes/federation/pkg/federation-controller/util"
"k8s.io/kubernetes/pkg/api"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
)
const (
UserAgentName = "Cluster-Controller"
)
type ClusterClient struct {
kubeClient *clientset.Clientset
}
func NewClusterClientSet(c *federation_v1beta1.Cluster) (*ClusterClient, error) {
clusterConfig, err := util.BuildClusterConfig(c)
if err != nil {
return nil, err
}
var clusterClientSet = ClusterClient{}
if clusterConfig != nil {
clusterClientSet.kubeClient = clientset.NewForConfigOrDie((restclient.AddUserAgent(clusterConfig, UserAgentName)))
if clusterClientSet.kubeClient == nil {
return nil, nil
}
}
return &clusterClientSet, nil
}
// GetClusterHealthStatus gets the kubernetes cluster health status by requesting "/healthz"
func (self *ClusterClient) GetClusterHealthStatus() *federation_v1beta1.ClusterStatus {
clusterStatus := federation_v1beta1.ClusterStatus{}
currentTime := metav1.Now()
newClusterReadyCondition := federation_v1beta1.ClusterCondition{
Type: federation_v1beta1.ClusterReady,
Status: v1.ConditionTrue,
Reason: "ClusterReady",
Message: "/healthz responded with ok",
LastProbeTime: currentTime,
LastTransitionTime: currentTime,
}
newClusterNotReadyCondition := federation_v1beta1.ClusterCondition{
Type: federation_v1beta1.ClusterReady,
Status: v1.ConditionFalse,
Reason: "ClusterNotReady",
Message: "/healthz responded without ok",
LastProbeTime: currentTime,
LastTransitionTime: currentTime,
}
newNodeOfflineCondition := federation_v1beta1.ClusterCondition{
Type: federation_v1beta1.ClusterOffline,
Status: v1.ConditionTrue,
Reason: "ClusterNotReachable",
Message: "cluster is not reachable",
LastProbeTime: currentTime,
LastTransitionTime: currentTime,
}
newNodeNotOfflineCondition := federation_v1beta1.ClusterCondition{
Type: federation_v1beta1.ClusterOffline,
Status: v1.ConditionFalse,
Reason: "ClusterReachable",
Message: "cluster is reachable",
LastProbeTime: currentTime,
LastTransitionTime: currentTime,
}
body, err := self.kubeClient.DiscoveryClient.RESTClient().Get().AbsPath("/healthz").Do().Raw()
if err != nil {
clusterStatus.Conditions = append(clusterStatus.Conditions, newNodeOfflineCondition)
} else {
if !strings.EqualFold(string(body), "ok") {
clusterStatus.Conditions = append(clusterStatus.Conditions, newClusterNotReadyCondition, newNodeNotOfflineCondition)
} else {
clusterStatus.Conditions = append(clusterStatus.Conditions, newClusterReadyCondition)
}
}
zones, region, err := self.GetClusterZones()
if err != nil {
glog.Warningf("Failed to get zones and region for cluster with client %v: %v", self, err)
} else {
clusterStatus.Zones = zones
clusterStatus.Region = region
}
return &clusterStatus
}
// GetClusterZones gets the kubernetes cluster zones and region by inspecting labels on nodes in the cluster.
func (self *ClusterClient) GetClusterZones() (zones []string, region string, err error) {
return getZoneNames(self.kubeClient)
}
// Find the name of the zone in which a Node is running
func getZoneNameForNode(node api.Node) (string, error) {
for key, value := range node.Labels {
if key == kubeletapis.LabelZoneFailureDomain {
return value, nil
}
}
return "", fmt.Errorf("Zone name for node %s not found. No label with key %s",
node.Name, kubeletapis.LabelZoneFailureDomain)
}
// Find the name of the region in which a Node is running
func getRegionNameForNode(node api.Node) (string, error) {
for key, value := range node.Labels {
if key == kubeletapis.LabelZoneRegion {
return value, nil
}
}
return "", fmt.Errorf("Region name for node %s not found. No label with key %s",
node.Name, kubeletapis.LabelZoneRegion)
}
// Find the names of all zones and the region in which we have nodes in this cluster.
func getZoneNames(client *clientset.Clientset) (zones []string, region string, err error) {
zoneNames := sets.NewString()
nodes, err := client.Core().Nodes().List(metav1.ListOptions{})
if err != nil {
glog.Errorf("Failed to list nodes while getting zone names: %v", err)
return nil, "", err
}
for i, node := range nodes.Items {
// TODO: quinton-hoole make this more efficient.
// For non-multi-zone clusters the zone will
// be identical for all nodes, so we only need to look at one node
// For multi-zone clusters we know at build time
// which zones are included. Rather get this info from there, because it's cheaper.
zoneName, err := getZoneNameForNode(node)
if err != nil {
return nil, "", err
}
zoneNames.Insert(zoneName)
if i == 0 {
region, err = getRegionNameForNode(node)
if err != nil {
return nil, "", err
}
}
}
return zoneNames.List(), region, nil
}

View file

@ -0,0 +1,209 @@
/*
Copyright 2016 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 cluster
import (
"strings"
"sync"
"time"
"github.com/golang/glog"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
federationv1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1"
clustercache "k8s.io/kubernetes/federation/client/cache"
federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset"
"k8s.io/kubernetes/pkg/controller"
)
type ClusterController struct {
// federationClient used to operate cluster
federationClient federationclientset.Interface
// clusterMonitorPeriod is the period for updating status of cluster
clusterMonitorPeriod time.Duration
mu sync.RWMutex
knownClusterSet sets.String
// clusterClusterStatusMap is a mapping of clusterName and cluster status of last sampling
clusterClusterStatusMap map[string]federationv1beta1.ClusterStatus
// clusterKubeClientMap is a mapping of clusterName and restclient
clusterKubeClientMap map[string]ClusterClient
// cluster framework and store
clusterController cache.Controller
clusterStore clustercache.StoreToClusterLister
}
// StartClusterController starts a new cluster controller
func StartClusterController(config *restclient.Config, stopChan <-chan struct{}, clusterMonitorPeriod time.Duration) {
restclient.AddUserAgent(config, "cluster-controller")
client := federationclientset.NewForConfigOrDie(config)
controller := newClusterController(client, clusterMonitorPeriod)
glog.Infof("Starting cluster controller")
controller.Run(stopChan)
}
// newClusterController returns a new cluster controller
func newClusterController(federationClient federationclientset.Interface, clusterMonitorPeriod time.Duration) *ClusterController {
cc := &ClusterController{
knownClusterSet: make(sets.String),
federationClient: federationClient,
clusterMonitorPeriod: clusterMonitorPeriod,
clusterClusterStatusMap: make(map[string]federationv1beta1.ClusterStatus),
clusterKubeClientMap: make(map[string]ClusterClient),
}
cc.clusterStore.Store, cc.clusterController = cache.NewInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
return cc.federationClient.Federation().Clusters().List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
return cc.federationClient.Federation().Clusters().Watch(options)
},
},
&federationv1beta1.Cluster{},
controller.NoResyncPeriodFunc(),
cache.ResourceEventHandlerFuncs{
DeleteFunc: cc.delFromClusterSet,
AddFunc: cc.addToClusterSet,
},
)
return cc
}
// delFromClusterSet delete a cluster from clusterSet and
// delete the corresponding restclient from the map clusterKubeClientMap
func (cc *ClusterController) delFromClusterSet(obj interface{}) {
cc.mu.Lock()
defer cc.mu.Unlock()
cluster := obj.(*federationv1beta1.Cluster)
cc.delFromClusterSetByName(cluster.Name)
}
// delFromClusterSetByName delete a cluster from clusterSet by name and
// delete the corresponding restclient from the map clusterKubeClientMap.
// Caller must make sure that they hold the mutex
func (cc *ClusterController) delFromClusterSetByName(clusterName string) {
glog.V(1).Infof("ClusterController observed a cluster deletion: %v", clusterName)
cc.knownClusterSet.Delete(clusterName)
delete(cc.clusterKubeClientMap, clusterName)
delete(cc.clusterClusterStatusMap, clusterName)
}
func (cc *ClusterController) addToClusterSet(obj interface{}) {
cc.mu.Lock()
defer cc.mu.Unlock()
cluster := obj.(*federationv1beta1.Cluster)
cc.addToClusterSetWithoutLock(cluster)
}
// addToClusterSetWithoutLock inserts the new cluster to clusterSet and create
// a corresponding restclient to map clusterKubeClientMap if the cluster is not
// known. Caller must make sure that they hold the mutex.
func (cc *ClusterController) addToClusterSetWithoutLock(cluster *federationv1beta1.Cluster) {
if cc.knownClusterSet.Has(cluster.Name) {
return
}
glog.V(1).Infof("ClusterController observed a new cluster: %v", cluster.Name)
cc.knownClusterSet.Insert(cluster.Name)
// create the restclient of cluster
restClient, err := NewClusterClientSet(cluster)
if err != nil || restClient == nil {
glog.Errorf("Failed to create corresponding restclient of kubernetes cluster: %v", err)
return
}
cc.clusterKubeClientMap[cluster.Name] = *restClient
}
// Run begins watching and syncing.
func (cc *ClusterController) Run(stopChan <-chan struct{}) {
defer utilruntime.HandleCrash()
go cc.clusterController.Run(stopChan)
// monitor cluster status periodically, in phase 1 we just get the health state from "/healthz"
go wait.Until(func() {
if err := cc.updateClusterStatus(); err != nil {
glog.Errorf("Error monitoring cluster status: %v", err)
}
}, cc.clusterMonitorPeriod, stopChan)
}
// updateClusterStatus checks cluster status and get the metrics from cluster's restapi
func (cc *ClusterController) updateClusterStatus() error {
clusters, err := cc.federationClient.Federation().Clusters().List(metav1.ListOptions{})
if err != nil {
return err
}
for _, cluster := range clusters.Items {
cc.mu.RLock()
// skip updating status of the cluster which is not yet added to knownClusterSet.
if !cc.knownClusterSet.Has(cluster.Name) {
cc.mu.RUnlock()
continue
}
clusterClient, clientFound := cc.clusterKubeClientMap[cluster.Name]
clusterStatusOld, statusFound := cc.clusterClusterStatusMap[cluster.Name]
cc.mu.RUnlock()
if !clientFound {
glog.Warningf("Failed to get client for cluster %s", cluster.Name)
continue
}
clusterStatusNew := clusterClient.GetClusterHealthStatus()
if !statusFound {
glog.Infof("There is no status stored for cluster: %v before", cluster.Name)
} else {
hasTransition := false
if len(clusterStatusNew.Conditions) != len(clusterStatusOld.Conditions) {
hasTransition = true
} else {
for i := 0; i < len(clusterStatusNew.Conditions); i++ {
if !(strings.EqualFold(string(clusterStatusNew.Conditions[i].Type), string(clusterStatusOld.Conditions[i].Type)) &&
strings.EqualFold(string(clusterStatusNew.Conditions[i].Status), string(clusterStatusOld.Conditions[i].Status))) {
hasTransition = true
break
}
}
}
if !hasTransition {
for j := 0; j < len(clusterStatusNew.Conditions); j++ {
clusterStatusNew.Conditions[j].LastTransitionTime = clusterStatusOld.Conditions[j].LastTransitionTime
}
}
}
cc.mu.Lock()
cc.clusterClusterStatusMap[cluster.Name] = *clusterStatusNew
cc.mu.Unlock()
cluster.Status = *clusterStatusNew
cluster, err := cc.federationClient.Federation().Clusters().UpdateStatus(&cluster)
if err != nil {
glog.Warningf("Failed to update the status of cluster: %v ,error is : %v", cluster.Name, err)
// Don't return err here, as we want to continue processing remaining clusters.
continue
}
}
return nil
}

View file

@ -0,0 +1,173 @@
/*
Copyright 2016 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 cluster
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/uuid"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
federationv1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1"
federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset"
"k8s.io/kubernetes/pkg/api/testapi"
)
func newCluster(clusterName string, serverUrl string) *federationv1beta1.Cluster {
cluster := federationv1beta1.Cluster{
TypeMeta: metav1.TypeMeta{APIVersion: testapi.Federation.GroupVersion().String()},
ObjectMeta: metav1.ObjectMeta{
UID: uuid.NewUUID(),
Name: clusterName,
},
Spec: federationv1beta1.ClusterSpec{
ServerAddressByClientCIDRs: []federationv1beta1.ServerAddressByClientCIDR{
{
ClientCIDR: "0.0.0.0/0",
ServerAddress: serverUrl,
},
},
},
}
return &cluster
}
func newClusterList(cluster *federationv1beta1.Cluster) *federationv1beta1.ClusterList {
clusterList := federationv1beta1.ClusterList{
TypeMeta: metav1.TypeMeta{APIVersion: testapi.Federation.GroupVersion().String()},
ListMeta: metav1.ListMeta{
SelfLink: "foobar",
},
Items: []federationv1beta1.Cluster{},
}
clusterList.Items = append(clusterList.Items, *cluster)
return &clusterList
}
// init a fake http handler, simulate a federation apiserver, response the "DELETE" "PUT" "GET" "UPDATE"
// when "canBeGotten" is false, means that user can not get the cluster cluster from apiserver
func createHttptestFakeHandlerForFederation(clusterList *federationv1beta1.ClusterList, canBeGotten bool) *http.HandlerFunc {
fakeHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
clusterListString, _ := json.Marshal(*clusterList)
w.Header().Set("Content-Type", "application/json")
switch r.Method {
case "PUT":
fmt.Fprintln(w, string(clusterListString))
case "GET":
if canBeGotten {
fmt.Fprintln(w, string(clusterListString))
} else {
fmt.Fprintln(w, "")
}
default:
fmt.Fprintln(w, "")
}
})
return &fakeHandler
}
// init a fake http handler, simulate a cluster apiserver, response the "/healthz"
// when "canBeGotten" is false, means that user can not get response from apiserver
func createHttptestFakeHandlerForCluster(canBeGotten bool) *http.HandlerFunc {
fakeHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.Method {
case "GET":
if canBeGotten {
fmt.Fprintln(w, "ok")
} else {
w.WriteHeader(http.StatusNotFound)
}
default:
fmt.Fprintln(w, "")
}
})
return &fakeHandler
}
func TestUpdateClusterStatusOK(t *testing.T) {
clusterName := "foobarCluster"
// create dummy httpserver
testClusterServer := httptest.NewServer(createHttptestFakeHandlerForCluster(true))
defer testClusterServer.Close()
federationCluster := newCluster(clusterName, testClusterServer.URL)
federationClusterList := newClusterList(federationCluster)
testFederationServer := httptest.NewServer(createHttptestFakeHandlerForFederation(federationClusterList, true))
defer testFederationServer.Close()
restClientCfg, err := clientcmd.BuildConfigFromFlags(testFederationServer.URL, "")
if err != nil {
t.Errorf("Failed to build client config")
}
federationClientSet := federationclientset.NewForConfigOrDie(restclient.AddUserAgent(restClientCfg, "cluster-controller"))
manager := newClusterController(federationClientSet, 5)
manager.addToClusterSet(federationCluster)
err = manager.updateClusterStatus()
if err != nil {
t.Errorf("Failed to Update Cluster Status: %v", err)
}
clusterStatus, found := manager.clusterClusterStatusMap[clusterName]
if !found {
t.Errorf("Failed to Update Cluster Status")
} else {
if (clusterStatus.Conditions[1].Status != v1.ConditionFalse) || (clusterStatus.Conditions[1].Type != federationv1beta1.ClusterOffline) {
t.Errorf("Failed to Update Cluster Status")
}
}
}
// Test races between informer's updates and routine updates of cluster status
// Issue https://github.com/kubernetes/kubernetes/issues/49958
func TestUpdateClusterRace(t *testing.T) {
clusterName := "foobarCluster"
// create dummy httpserver
testClusterServer := httptest.NewServer(createHttptestFakeHandlerForCluster(true))
defer testClusterServer.Close()
federationCluster := newCluster(clusterName, testClusterServer.URL)
federationClusterList := newClusterList(federationCluster)
testFederationServer := httptest.NewServer(createHttptestFakeHandlerForFederation(federationClusterList, true))
defer testFederationServer.Close()
restClientCfg, err := clientcmd.BuildConfigFromFlags(testFederationServer.URL, "")
if err != nil {
t.Errorf("Failed to build client config")
}
federationClientSet := federationclientset.NewForConfigOrDie(restclient.AddUserAgent(restClientCfg, "cluster-controller"))
manager := newClusterController(federationClientSet, 1*time.Millisecond)
stop := make(chan struct{})
manager.Run(stop)
// try to trigger the race in UpdateClusterStatus
for i := 0; i < 10; i++ {
manager.addToClusterSet(federationCluster)
manager.delFromClusterSet(federationCluster)
}
close(stop)
}

View file

@ -0,0 +1,18 @@
/*
Copyright 2016 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 cluster contains code for syncing cluster
package cluster // import "k8s.io/kubernetes/federation/pkg/federation-controller/cluster"

View file

@ -0,0 +1,19 @@
/*
Copyright 2015 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 federation_controller contains code for controllers (like the cluster
// controller).
package federation_controller // import "k8s.io/kubernetes/federation/pkg/federation-controller"

Some files were not shown because too many files have changed in this diff Show more