Merge pull request #9720 from justinsb/aws_routes

Refactor Routes, and dynamically configure minion CIDRs on AWS
This commit is contained in:
Satnam Singh 2015-06-18 17:16:29 -07:00
commit 9f32599bee
15 changed files with 266 additions and 107 deletions

View File

@ -41,10 +41,10 @@ MASTER_NAME="${INSTANCE_PREFIX}-master"
MINION_NAMES=($(eval echo ${INSTANCE_PREFIX}-minion-{1..${NUM_MINIONS}})) MINION_NAMES=($(eval echo ${INSTANCE_PREFIX}-minion-{1..${NUM_MINIONS}}))
MASTER_TAG="${INSTANCE_PREFIX}-master" MASTER_TAG="${INSTANCE_PREFIX}-master"
MINION_TAG="${INSTANCE_PREFIX}-minion" MINION_TAG="${INSTANCE_PREFIX}-minion"
MINION_IP_RANGES=($(eval echo "10.244.{1..${NUM_MINIONS}}.0/24"))
MINION_SCOPES="" MINION_SCOPES=""
POLL_SLEEP_INTERVAL=3 POLL_SLEEP_INTERVAL=3
SERVICE_CLUSTER_IP_RANGE="10.0.0.0/16" # formerly PORTAL_NET SERVICE_CLUSTER_IP_RANGE="10.0.0.0/16" # formerly PORTAL_NET
CLUSTER_IP_RANGE="${CLUSTER_IP_RANGE:-10.244.0.0/16}"
MASTER_IP_RANGE="${MASTER_IP_RANGE:-10.246.0.0/24}" MASTER_IP_RANGE="${MASTER_IP_RANGE:-10.246.0.0/24}"
# If set to Elastic IP, master instance will be associated with this IP. # If set to Elastic IP, master instance will be associated with this IP.
# If set to auto, a new Elastic IP will be aquired # If set to auto, a new Elastic IP will be aquired

View File

@ -37,10 +37,10 @@ MASTER_NAME="${INSTANCE_PREFIX}-master"
MINION_NAMES=($(eval echo ${INSTANCE_PREFIX}-minion-{1..${NUM_MINIONS}})) MINION_NAMES=($(eval echo ${INSTANCE_PREFIX}-minion-{1..${NUM_MINIONS}}))
MASTER_TAG="${INSTANCE_PREFIX}-master" MASTER_TAG="${INSTANCE_PREFIX}-master"
MINION_TAG="${INSTANCE_PREFIX}-minion" MINION_TAG="${INSTANCE_PREFIX}-minion"
MINION_IP_RANGES=($(eval echo "10.244.{1..${NUM_MINIONS}}.0/24"))
MINION_SCOPES="" MINION_SCOPES=""
POLL_SLEEP_INTERVAL=3 POLL_SLEEP_INTERVAL=3
SERVICE_CLUSTER_IP_RANGE="10.0.0.0/16" # formerly PORTAL_NET SERVICE_CLUSTER_IP_RANGE="10.0.0.0/16" # formerly PORTAL_NET
CLUSTER_IP_RANGE="${CLUSTER_IP_RANGE:-10.245.0.0/16}"
MASTER_IP_RANGE="${MASTER_IP_RANGE:-10.246.0.0/24}" MASTER_IP_RANGE="${MASTER_IP_RANGE:-10.246.0.0/24}"
# If set to Elastic IP, master instance will be associated with this IP. # If set to Elastic IP, master instance will be associated with this IP.
# If set to auto, a new Elastic IP will be aquired # If set to auto, a new Elastic IP will be aquired

View File

@ -30,8 +30,8 @@ function detect-minion-image (){
function generate-minion-user-data() { function generate-minion-user-data() {
i=$1 i=$1
# TODO(bakins): Is this actually used?
MINION_PRIVATE_IP=$INTERNAL_IP_BASE.1${i} MINION_PRIVATE_IP=$INTERNAL_IP_BASE.1${i}
MINION_IP_RANGE=${MINION_IP_RANGES[$i]}
# this is a bit of a hack. We make all of our "variables" in # this is a bit of a hack. We make all of our "variables" in
# our cloud config controlled by env vars from this script # our cloud config controlled by env vars from this script
@ -44,7 +44,6 @@ function generate-minion-user-data() {
DNS_SERVER_IP=$(yaml-quote ${DNS_SERVER_IP:-}) DNS_SERVER_IP=$(yaml-quote ${DNS_SERVER_IP:-})
DNS_DOMAIN=$(yaml-quote ${DNS_DOMAIN:-}) DNS_DOMAIN=$(yaml-quote ${DNS_DOMAIN:-})
MASTER_IP=$(yaml-quote ${MASTER_INTERNAL_IP}) MASTER_IP=$(yaml-quote ${MASTER_INTERNAL_IP})
MINION_IP_RANGE=$(yaml-quote ${MINION_IP_RANGE})
MINION_IP=$(yaml-quote ${MINION_PRIVATE_IP}) MINION_IP=$(yaml-quote ${MINION_PRIVATE_IP})
KUBELET_TOKEN=$(yaml-quote ${KUBELET_TOKEN:-}) KUBELET_TOKEN=$(yaml-quote ${KUBELET_TOKEN:-})
KUBE_PROXY_TOKEN=$(yaml-quote ${KUBE_PROXY_TOKEN:-}) KUBE_PROXY_TOKEN=$(yaml-quote ${KUBE_PROXY_TOKEN:-})

View File

@ -22,6 +22,8 @@ mkdir -p /srv/salt-overlay/pillar
cat <<EOF >/srv/salt-overlay/pillar/cluster-params.sls cat <<EOF >/srv/salt-overlay/pillar/cluster-params.sls
instance_prefix: '$(echo "$INSTANCE_PREFIX" | sed -e "s/'/''/g")' instance_prefix: '$(echo "$INSTANCE_PREFIX" | sed -e "s/'/''/g")'
node_instance_prefix: '$(echo "$NODE_INSTANCE_PREFIX" | sed -e "s/'/''/g")' node_instance_prefix: '$(echo "$NODE_INSTANCE_PREFIX" | sed -e "s/'/''/g")'
cluster_cidr: '$(echo "$CLUSTER_IP_RANGE" | sed -e "s/'/''/g")'
allocate_node_cidrs: '$(echo "$ALLOCATE_NODE_CIDRS" | sed -e "s/'/''/g")'
service_cluster_ip_range: '$(echo "$SERVICE_CLUSTER_IP_RANGE" | sed -e "s/'/''/g")' service_cluster_ip_range: '$(echo "$SERVICE_CLUSTER_IP_RANGE" | sed -e "s/'/''/g")'
enable_cluster_monitoring: '$(echo "$ENABLE_CLUSTER_MONITORING" | sed -e "s/'/''/g")' enable_cluster_monitoring: '$(echo "$ENABLE_CLUSTER_MONITORING" | sed -e "s/'/''/g")'
enable_node_monitoring: '$(echo "$ENABLE_NODE_MONITORING" | sed -e "s/'/''/g")' enable_node_monitoring: '$(echo "$ENABLE_NODE_MONITORING" | sed -e "s/'/''/g")'

View File

@ -26,7 +26,7 @@ cat <<EOF >/etc/salt/minion.d/grains.conf
grains: grains:
roles: roles:
- kubernetes-pool - kubernetes-pool
cbr-cidr: $MINION_IP_RANGE cbr-cidr: 10.123.45.0/30
cloud: aws cloud: aws
EOF EOF

View File

@ -29,7 +29,6 @@ function generate-minion-user-data {
# We pipe this to the ami as a startup script in the user-data field. Requires a compatible ami # We pipe this to the ami as a startup script in the user-data field. Requires a compatible ami
echo "#! /bin/bash" echo "#! /bin/bash"
echo "SALT_MASTER='${MASTER_INTERNAL_IP}'" echo "SALT_MASTER='${MASTER_INTERNAL_IP}'"
echo "MINION_IP_RANGE='${MINION_IP_RANGES[$i]}'"
echo "DOCKER_OPTS='${EXTRA_DOCKER_OPTS:-}'" echo "DOCKER_OPTS='${EXTRA_DOCKER_OPTS:-}'"
echo "readonly DOCKER_STORAGE='${DOCKER_STORAGE:-}'" echo "readonly DOCKER_STORAGE='${DOCKER_STORAGE:-}'"
grep -v "^#" "${KUBE_ROOT}/cluster/aws/templates/common.sh" grep -v "^#" "${KUBE_ROOT}/cluster/aws/templates/common.sh"

View File

@ -22,6 +22,8 @@ KUBE_ROOT=$(dirname "${BASH_SOURCE}")/../..
source "${KUBE_ROOT}/cluster/aws/${KUBE_CONFIG_FILE-"config-default.sh"}" source "${KUBE_ROOT}/cluster/aws/${KUBE_CONFIG_FILE-"config-default.sh"}"
source "${KUBE_ROOT}/cluster/common.sh" source "${KUBE_ROOT}/cluster/common.sh"
ALLOCATE_NODE_CIDRS=true
case "${KUBE_OS_DISTRIBUTION}" in case "${KUBE_OS_DISTRIBUTION}" in
ubuntu|wheezy|coreos) ubuntu|wheezy|coreos)
source "${KUBE_ROOT}/cluster/aws/${KUBE_OS_DISTRIBUTION}/util.sh" source "${KUBE_ROOT}/cluster/aws/${KUBE_OS_DISTRIBUTION}/util.sh"
@ -695,6 +697,8 @@ function kube-up {
echo "readonly SALT_MASTER='${MASTER_INTERNAL_IP}'" echo "readonly SALT_MASTER='${MASTER_INTERNAL_IP}'"
echo "readonly INSTANCE_PREFIX='${INSTANCE_PREFIX}'" echo "readonly INSTANCE_PREFIX='${INSTANCE_PREFIX}'"
echo "readonly NODE_INSTANCE_PREFIX='${INSTANCE_PREFIX}-minion'" echo "readonly NODE_INSTANCE_PREFIX='${INSTANCE_PREFIX}-minion'"
echo "readonly CLUSTER_IP_RANGE='${CLUSTER_IP_RANGE}'"
echo "readonly ALLOCATE_NODE_CIDRS='${ALLOCATE_NODE_CIDRS}'"
echo "readonly SERVER_BINARY_TAR_URL='${SERVER_BINARY_TAR_URL}'" echo "readonly SERVER_BINARY_TAR_URL='${SERVER_BINARY_TAR_URL}'"
echo "readonly SALT_TAR_URL='${SALT_TAR_URL}'" echo "readonly SALT_TAR_URL='${SALT_TAR_URL}'"
echo "readonly ZONE='${ZONE}'" echo "readonly ZONE='${ZONE}'"
@ -854,7 +858,8 @@ function kube-up {
MINION_IDS[$i]=$minion_id MINION_IDS[$i]=$minion_id
done done
# Add routes to minions # Configure minion networking
# TODO(justinsb): Check if we can change source-dest-check before instance fully running
for (( i=0; i<${#MINION_NAMES[@]}; i++)); do for (( i=0; i<${#MINION_NAMES[@]}; i++)); do
# We are not able to add a route to the instance until that instance is in "running" state. # We are not able to add a route to the instance until that instance is in "running" state.
# This is quite an ugly solution to this problem. In Bash 4 we could use assoc. arrays to do this for # This is quite an ugly solution to this problem. In Bash 4 we could use assoc. arrays to do this for
@ -864,7 +869,6 @@ function kube-up {
echo "Minion ${MINION_NAMES[$i]} running" echo "Minion ${MINION_NAMES[$i]} running"
sleep 10 sleep 10
$AWS_CMD modify-instance-attribute --instance-id $minion_id --source-dest-check '{"Value": false}' > $LOG $AWS_CMD modify-instance-attribute --instance-id $minion_id --source-dest-check '{"Value": false}' > $LOG
$AWS_CMD create-route --route-table-id $ROUTE_TABLE_ID --destination-cidr-block ${MINION_IP_RANGES[$i]} --instance-id $minion_id > $LOG
done done
FAIL=0 FAIL=0

View File

@ -86,6 +86,10 @@ type EC2 interface {
DescribeSubnets(*ec2.DescribeSubnetsInput) ([]*ec2.Subnet, error) DescribeSubnets(*ec2.DescribeSubnetsInput) ([]*ec2.Subnet, error)
CreateTags(*ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error) CreateTags(*ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error)
DescribeRouteTables(request *ec2.DescribeRouteTablesInput) ([]*ec2.RouteTable, error)
CreateRoute(request *ec2.CreateRouteInput) (*ec2.CreateRouteOutput, error)
DeleteRoute(request *ec2.DeleteRouteInput) (*ec2.DeleteRouteOutput, error)
} }
// This is a simple pass-through of the ELB client interface, which allows for testing // This is a simple pass-through of the ELB client interface, which allows for testing
@ -393,6 +397,23 @@ func (s *awsSdkEC2) CreateTags(request *ec2.CreateTagsInput) (*ec2.CreateTagsOut
return s.ec2.CreateTags(request) return s.ec2.CreateTags(request)
} }
func (s *awsSdkEC2) DescribeRouteTables(request *ec2.DescribeRouteTablesInput) ([]*ec2.RouteTable, error) {
// Not paged
response, err := s.ec2.DescribeRouteTables(request)
if err != nil {
return nil, fmt.Errorf("error listing AWS route tables: %v", err)
}
return response.RouteTables, nil
}
func (s *awsSdkEC2) CreateRoute(request *ec2.CreateRouteInput) (*ec2.CreateRouteOutput, error) {
return s.ec2.CreateRoute(request)
}
func (s *awsSdkEC2) DeleteRoute(request *ec2.DeleteRouteInput) (*ec2.DeleteRouteOutput, error) {
return s.ec2.DeleteRoute(request)
}
func init() { func init() {
cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) { cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) {
creds := credentials.NewChainCredentials( creds := credentials.NewChainCredentials(
@ -550,7 +571,7 @@ func (aws *AWSCloud) Zones() (cloudprovider.Zones, bool) {
// Routes returns an implementation of Routes for Amazon Web Services. // Routes returns an implementation of Routes for Amazon Web Services.
func (aws *AWSCloud) Routes() (cloudprovider.Routes, bool) { func (aws *AWSCloud) Routes() (cloudprovider.Routes, bool) {
return nil, false return aws, true
} }
// NodeAddresses is an implementation of Instances.NodeAddresses. // NodeAddresses is an implementation of Instances.NodeAddresses.

View File

@ -0,0 +1,112 @@
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
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 aws_cloud
import (
"fmt"
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
)
func (s *AWSCloud) findRouteTable(clusterName string) (*ec2.RouteTable, error) {
// This should be unnecessary (we already filter on TagNameKubernetesCluster,
// and something is broken if cluster name doesn't match, but anyway...
// TODO: All clouds should be cluster-aware by default
filters := []*ec2.Filter{newEc2Filter("tag:"+TagNameKubernetesCluster, clusterName)}
request := &ec2.DescribeRouteTablesInput{Filters: s.addFilters(filters)}
tables, err := s.ec2.DescribeRouteTables(request)
if err != nil {
return nil, err
}
if len(tables) == 0 {
return nil, fmt.Errorf("unable to find route table for AWS cluster: %s", clusterName)
}
if len(tables) != 1 {
return nil, fmt.Errorf("found multiple matching AWS route tables for AWS cluster: %s", clusterName)
}
return tables[0], nil
}
// ListRoutes implements Routes.ListRoutes
// List all routes that match the filter
func (s *AWSCloud) ListRoutes(clusterName string) ([]*cloudprovider.Route, error) {
table, err := s.findRouteTable(clusterName)
if err != nil {
return nil, err
}
var routes []*cloudprovider.Route
for _, r := range table.Routes {
instanceID := orEmpty(r.InstanceID)
destinationCIDR := orEmpty(r.DestinationCIDRBlock)
if instanceID == "" || destinationCIDR == "" {
continue
}
routeName := clusterName + "-" + destinationCIDR
routes = append(routes, &cloudprovider.Route{routeName, instanceID, destinationCIDR})
}
return routes, nil
}
// CreateRoute implements Routes.CreateRoute
// Create the described route
func (s *AWSCloud) CreateRoute(clusterName string, nameHint string, route *cloudprovider.Route) error {
table, err := s.findRouteTable(clusterName)
if err != nil {
return err
}
request := &ec2.CreateRouteInput{}
// TODO: use ClientToken for idempotency?
request.DestinationCIDRBlock = aws.String(route.DestinationCIDR)
request.InstanceID = aws.String(route.TargetInstance)
request.RouteTableID = table.RouteTableID
_, err = s.ec2.CreateRoute(request)
if err != nil {
return fmt.Errorf("error creating AWS route (%s): %v", route.DestinationCIDR, err)
}
return nil
}
// DeleteRoute implements Routes.DeleteRoute
// Delete the specified route
func (s *AWSCloud) DeleteRoute(clusterName string, route *cloudprovider.Route) error {
table, err := s.findRouteTable(clusterName)
if err != nil {
return err
}
request := &ec2.DeleteRouteInput{}
request.DestinationCIDRBlock = aws.String(route.DestinationCIDR)
request.RouteTableID = table.RouteTableID
_, err = s.ec2.DeleteRoute(request)
if err != nil {
return fmt.Errorf("error deleting AWS route (%s): %v", route.DestinationCIDR, err)
}
return nil
}

View File

@ -360,6 +360,18 @@ func (ec2 *FakeEC2) CreateTags(*ec2.CreateTagsInput) (*ec2.CreateTagsOutput, err
panic("Not implemented") panic("Not implemented")
} }
func (s *FakeEC2) DescribeRouteTables(request *ec2.DescribeRouteTablesInput) ([]*ec2.RouteTable, error) {
panic("Not implemented")
}
func (s *FakeEC2) CreateRoute(request *ec2.CreateRouteInput) (*ec2.CreateRouteOutput, error) {
panic("Not implemented")
}
func (s *FakeEC2) DeleteRoute(request *ec2.DeleteRouteInput) (*ec2.DeleteRouteOutput, error) {
panic("Not implemented")
}
type FakeELB struct { type FakeELB struct {
aws *FakeAWSServices aws *FakeAWSServices
} }

View File

@ -120,6 +120,7 @@ type Instances interface {
// Route is a representation of an advanced routing rule. // Route is a representation of an advanced routing rule.
type Route struct { type Route struct {
// Name is the name of the routing rule in the cloud-provider. // Name is the name of the routing rule in the cloud-provider.
// It will be ignored in a Create (although nameHint may influence it)
Name string Name string
// TargetInstance is the name of the instance as specified in routing rules // TargetInstance is the name of the instance as specified in routing rules
// for the cloud-provider (in gce: the Instance Name). // for the cloud-provider (in gce: the Instance Name).
@ -127,18 +128,19 @@ type Route struct {
// Destination CIDR is the CIDR format IP range that this routing rule // Destination CIDR is the CIDR format IP range that this routing rule
// applies to. // applies to.
DestinationCIDR string DestinationCIDR string
// Description is a free-form string. It can be useful for tagging Routes.
Description string
} }
// Routes is an abstract, pluggable interface for advanced routing rules. // Routes is an abstract, pluggable interface for advanced routing rules.
type Routes interface { type Routes interface {
// List all routes that match the filter // List all managed routes that belong to the specified clusterName
ListRoutes(filter string) ([]*Route, error) ListRoutes(clusterName string) ([]*Route, error)
// Create the described route // Create the described managed route
CreateRoute(route *Route) error // route.Name will be ignored, although the cloud-provider may use nameHint
// Delete the specified route // to create a more user-meaningful name.
DeleteRoute(name string) error CreateRoute(clusterName string, nameHint string, route *Route) error
// Delete the specified managed route
// Route should be as returned by ListRoutes
DeleteRoute(clusterName string, route *Route) error
} }
var InstanceNotFound = errors.New("instance not found") var InstanceNotFound = errors.New("instance not found")

View File

@ -58,11 +58,16 @@ type FakeCloud struct {
ExternalIP net.IP ExternalIP net.IP
Balancers []FakeBalancer Balancers []FakeBalancer
UpdateCalls []FakeUpdateBalancerCall UpdateCalls []FakeUpdateBalancerCall
RouteMap map[string]*cloudprovider.Route RouteMap map[string]*FakeRoute
Lock sync.Mutex Lock sync.Mutex
cloudprovider.Zone cloudprovider.Zone
} }
type FakeRoute struct {
ClusterName string
Route cloudprovider.Route
}
func (f *FakeCloud) addCall(desc string) { func (f *FakeCloud) addCall(desc string) {
f.Calls = append(f.Calls, desc) f.Calls = append(f.Calls, desc)
} }
@ -198,35 +203,42 @@ func (f *FakeCloud) GetNodeResources(name string) (*api.NodeResources, error) {
return f.NodeResources, f.Err return f.NodeResources, f.Err
} }
func (f *FakeCloud) ListRoutes(filter string) ([]*cloudprovider.Route, error) { func (f *FakeCloud) ListRoutes(clusterName string) ([]*cloudprovider.Route, error) {
f.Lock.Lock() f.Lock.Lock()
defer f.Lock.Unlock() defer f.Lock.Unlock()
f.addCall("list-routes") f.addCall("list-routes")
var routes []*cloudprovider.Route var routes []*cloudprovider.Route
for _, route := range f.RouteMap { for _, fakeRoute := range f.RouteMap {
if match, _ := regexp.MatchString(filter, route.Name); match { if clusterName == fakeRoute.ClusterName {
routes = append(routes, route) routeCopy := fakeRoute.Route
routes = append(routes, &routeCopy)
} }
} }
return routes, f.Err return routes, f.Err
} }
func (f *FakeCloud) CreateRoute(route *cloudprovider.Route) error { func (f *FakeCloud) CreateRoute(clusterName string, nameHint string, route *cloudprovider.Route) error {
f.Lock.Lock() f.Lock.Lock()
defer f.Lock.Unlock() defer f.Lock.Unlock()
f.addCall("create-route") f.addCall("create-route")
if _, exists := f.RouteMap[route.Name]; exists { name := clusterName + "-" + nameHint
f.Err = fmt.Errorf("route with name %q already exists") if _, exists := f.RouteMap[name]; exists {
f.Err = fmt.Errorf("route %q already exists", name)
return f.Err return f.Err
} }
f.RouteMap[route.Name] = route fakeRoute := FakeRoute{}
fakeRoute.Route = *route
fakeRoute.Route.Name = name
fakeRoute.ClusterName = clusterName
f.RouteMap[name] = &fakeRoute
return nil return nil
} }
func (f *FakeCloud) DeleteRoute(name string) error { func (f *FakeCloud) DeleteRoute(clusterName string, route *cloudprovider.Route) error {
f.Lock.Lock() f.Lock.Lock()
defer f.Lock.Unlock() defer f.Lock.Unlock()
f.addCall("delete-route") f.addCall("delete-route")
name := route.Name
if _, exists := f.RouteMap[name]; !exists { if _, exists := f.RouteMap[name]; !exists {
f.Err = fmt.Errorf("no route found with name %q", name) f.Err = fmt.Errorf("no route found with name %q", name)
return f.Err return f.Err

View File

@ -49,6 +49,8 @@ const (
INTERNAL_IP_METADATA_URL = "http://169.254.169.254/computeMetadata/v1/instance/network-interfaces/0/ip" INTERNAL_IP_METADATA_URL = "http://169.254.169.254/computeMetadata/v1/instance/network-interfaces/0/ip"
) )
const k8sNodeRouteTag = "k8s-node-route"
// GCECloud is an implementation of Interface, TCPLoadBalancer and Instances for Google Compute Engine. // GCECloud is an implementation of Interface, TCPLoadBalancer and Instances for Google Compute Engine.
type GCECloud struct { type GCECloud struct {
service *compute.Service service *compute.Service
@ -631,11 +633,19 @@ func getMetadataValue(metadata *compute.Metadata, key string) (string, bool) {
return "", false return "", false
} }
func (gce *GCECloud) ListRoutes(filter string) ([]*cloudprovider.Route, error) { func truncateClusterName(clusterName string) string {
listCall := gce.service.Routes.List(gce.projectID) if len(clusterName) > 26 {
if len(filter) > 0 { return clusterName[:26]
listCall = listCall.Filter("name eq " + filter)
} }
return clusterName
}
func (gce *GCECloud) ListRoutes(clusterName string) ([]*cloudprovider.Route, error) {
listCall := gce.service.Routes.List(gce.projectID)
prefix := truncateClusterName(clusterName)
listCall = listCall.Filter("name eq " + prefix + "-.*")
res, err := listCall.Do() res, err := listCall.Do()
if err != nil { if err != nil {
return nil, err return nil, err
@ -645,21 +655,32 @@ func (gce *GCECloud) ListRoutes(filter string) ([]*cloudprovider.Route, error) {
if path.Base(r.Network) != gce.networkName { if path.Base(r.Network) != gce.networkName {
continue continue
} }
// Not managed if route description != "k8s-node-route"
if r.Description != k8sNodeRouteTag {
continue
}
// Not managed if route name doesn't start with <clusterName>
if !strings.HasPrefix(r.Name, prefix) {
continue
}
target := path.Base(r.NextHopInstance) target := path.Base(r.NextHopInstance)
routes = append(routes, &cloudprovider.Route{r.Name, target, r.DestRange, r.Description}) routes = append(routes, &cloudprovider.Route{r.Name, target, r.DestRange})
} }
return routes, nil return routes, nil
} }
func (gce *GCECloud) CreateRoute(route *cloudprovider.Route) error { func (gce *GCECloud) CreateRoute(clusterName string, nameHint string, route *cloudprovider.Route) error {
routeName := truncateClusterName(clusterName) + "-" + nameHint
instanceName := canonicalizeInstanceName(route.TargetInstance) instanceName := canonicalizeInstanceName(route.TargetInstance)
insertOp, err := gce.service.Routes.Insert(gce.projectID, &compute.Route{ insertOp, err := gce.service.Routes.Insert(gce.projectID, &compute.Route{
Name: route.Name, Name: routeName,
DestRange: route.DestinationCIDR, DestRange: route.DestinationCIDR,
NextHopInstance: fmt.Sprintf("zones/%s/instances/%s", gce.zone, instanceName), NextHopInstance: fmt.Sprintf("zones/%s/instances/%s", gce.zone, instanceName),
Network: fmt.Sprintf("global/networks/%s", gce.networkName), Network: fmt.Sprintf("global/networks/%s", gce.networkName),
Priority: 1000, Priority: 1000,
Description: route.Description, Description: k8sNodeRouteTag,
}).Do() }).Do()
if err != nil { if err != nil {
return err return err
@ -667,9 +688,8 @@ func (gce *GCECloud) CreateRoute(route *cloudprovider.Route) error {
return gce.waitForGlobalOp(insertOp) return gce.waitForGlobalOp(insertOp)
} }
func (gce *GCECloud) DeleteRoute(name string) error { func (gce *GCECloud) DeleteRoute(clusterName string, route *cloudprovider.Route) error {
instanceName := canonicalizeInstanceName(name) deleteOp, err := gce.service.Routes.Delete(gce.projectID, route.Name).Do()
deleteOp, err := gce.service.Routes.Delete(gce.projectID, instanceName).Do()
if err != nil { if err != nil {
return err return err
} }

View File

@ -19,7 +19,6 @@ package routecontroller
import ( import (
"fmt" "fmt"
"net" "net"
"strings"
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
@ -38,8 +37,6 @@ type RouteController struct {
clusterCIDR *net.IPNet clusterCIDR *net.IPNet
} }
const k8sNodeRouteTag = "k8s-node-route"
func New(routes cloudprovider.Routes, kubeClient client.Interface, clusterName string, clusterCIDR *net.IPNet) *RouteController { func New(routes cloudprovider.Routes, kubeClient client.Interface, clusterName string, clusterCIDR *net.IPNet) *RouteController {
return &RouteController{ return &RouteController{
routes: routes, routes: routes,
@ -58,7 +55,7 @@ func (rc *RouteController) Run(syncPeriod time.Duration) {
} }
func (rc *RouteController) reconcileNodeRoutes() error { func (rc *RouteController) reconcileNodeRoutes() error {
routeList, err := rc.routes.ListRoutes(rc.truncatedClusterName() + "-.*") routeList, err := rc.routes.ListRoutes(rc.clusterName)
if err != nil { if err != nil {
return fmt.Errorf("error listing routes: %v", err) return fmt.Errorf("error listing routes: %v", err)
} }
@ -85,16 +82,15 @@ func (rc *RouteController) reconcile(nodes []api.Node, routes []*cloudprovider.R
if r == nil || r.DestinationCIDR != node.Spec.PodCIDR { if r == nil || r.DestinationCIDR != node.Spec.PodCIDR {
// If not, create the route. // If not, create the route.
route := &cloudprovider.Route{ route := &cloudprovider.Route{
Name: rc.truncatedClusterName() + "-" + string(node.UID),
TargetInstance: node.Name, TargetInstance: node.Name,
DestinationCIDR: node.Spec.PodCIDR, DestinationCIDR: node.Spec.PodCIDR,
Description: k8sNodeRouteTag,
} }
go func(route *cloudprovider.Route) { nameHint := string(node.UID)
if err := rc.routes.CreateRoute(route); err != nil { go func(nameHint string, route *cloudprovider.Route) {
glog.Errorf("Could not create route %s: %v", route.Name, err) if err := rc.routes.CreateRoute(rc.clusterName, nameHint, route); err != nil {
glog.Errorf("Could not create route %s %s: %v", nameHint, route.DestinationCIDR, err)
} }
}(route) }(nameHint, route)
} }
nodeCIDRs[node.Name] = node.Spec.PodCIDR nodeCIDRs[node.Name] = node.Spec.PodCIDR
} }
@ -103,24 +99,17 @@ func (rc *RouteController) reconcile(nodes []api.Node, routes []*cloudprovider.R
// Check if this route applies to a node we know about & has correct CIDR. // Check if this route applies to a node we know about & has correct CIDR.
if nodeCIDRs[route.TargetInstance] != route.DestinationCIDR { if nodeCIDRs[route.TargetInstance] != route.DestinationCIDR {
// Delete the route. // Delete the route.
go func(routeName string) { go func(route *cloudprovider.Route) {
if err := rc.routes.DeleteRoute(routeName); err != nil { if err := rc.routes.DeleteRoute(rc.clusterName, route); err != nil {
glog.Errorf("Could not delete route %s: %v", routeName, err) glog.Errorf("Could not delete route %s %s: %v", route.Name, route.DestinationCIDR, err)
} }
}(route.Name) }(route)
} }
} }
} }
return nil return nil
} }
func (rc *RouteController) truncatedClusterName() string {
if len(rc.clusterName) > 26 {
return rc.clusterName[:26]
}
return rc.clusterName
}
func (rc *RouteController) isResponsibleForRoute(route *cloudprovider.Route) bool { func (rc *RouteController) isResponsibleForRoute(route *cloudprovider.Route) bool {
_, cidr, err := net.ParseCIDR(route.DestinationCIDR) _, cidr, err := net.ParseCIDR(route.DestinationCIDR)
if err != nil { if err != nil {
@ -135,13 +124,5 @@ func (rc *RouteController) isResponsibleForRoute(route *cloudprovider.Route) boo
if !rc.clusterCIDR.Contains(cidr.IP) || !rc.clusterCIDR.Contains(lastIP) { if !rc.clusterCIDR.Contains(cidr.IP) || !rc.clusterCIDR.Contains(lastIP) {
return false return false
} }
// Not responsible if route name doesn't start with <clusterName>
if !strings.HasPrefix(route.Name, rc.clusterName) {
return false
}
// Not responsible if route description != "k8s-node-route"
if route.Description != k8sNodeRouteTag {
return false
}
return true return true
} }

View File

@ -33,27 +33,20 @@ func TestIsResponsibleForRoute(t *testing.T) {
clusterCIDR string clusterCIDR string
routeName string routeName string
routeCIDR string routeCIDR string
routeDescription string
expectedResponsible bool expectedResponsible bool
}{ }{
// Routes that belong to this cluster // Routes that belong to this cluster
{"10.244.0.0/16", myClusterRoute, "10.244.0.0/24", "k8s-node-route", true}, {"10.244.0.0/16", myClusterRoute, "10.244.0.0/24", true},
{"10.244.0.0/16", myClusterRoute, "10.244.10.0/24", "k8s-node-route", true}, {"10.244.0.0/16", myClusterRoute, "10.244.10.0/24", true},
{"10.244.0.0/16", myClusterRoute, "10.244.255.0/24", "k8s-node-route", true}, {"10.244.0.0/16", myClusterRoute, "10.244.255.0/24", true},
{"10.244.0.0/14", myClusterRoute, "10.244.0.0/24", "k8s-node-route", true}, {"10.244.0.0/14", myClusterRoute, "10.244.0.0/24", true},
{"10.244.0.0/14", myClusterRoute, "10.247.255.0/24", "k8s-node-route", true}, {"10.244.0.0/14", myClusterRoute, "10.247.255.0/24", true},
// Routes inside our cidr, but not named how we would have named them
{"10.244.0.0/16", "background-cluster-route", "10.244.0.0/16", "k8s-node-route", false},
{"10.244.0.0/16", "special-single-route", "10.244.12.34/32", "k8s-node-route", false},
// Routes inside our cidr, but not tagged how we would have tagged them in the description
{"10.244.0.0/16", "my-awesome-cluster-background", "10.244.0.0/16", "", false},
{"10.244.0.0/16", "my-awesome-cluster-single-route", "10.244.12.34/32", "this is a route", false},
// Routes that match our naming/tagging scheme, but are outside our cidr // Routes that match our naming/tagging scheme, but are outside our cidr
{"10.244.0.0/16", myClusterRoute, "10.224.0.0/24", "k8s-node-route", false}, {"10.244.0.0/16", myClusterRoute, "10.224.0.0/24", false},
{"10.244.0.0/16", myClusterRoute, "10.0.10.0/24", "k8s-node-route", false}, {"10.244.0.0/16", myClusterRoute, "10.0.10.0/24", false},
{"10.244.0.0/16", myClusterRoute, "10.255.255.0/24", "k8s-node-route", false}, {"10.244.0.0/16", myClusterRoute, "10.255.255.0/24", false},
{"10.244.0.0/14", myClusterRoute, "10.248.0.0/24", "k8s-node-route", false}, {"10.244.0.0/14", myClusterRoute, "10.248.0.0/24", false},
{"10.244.0.0/14", myClusterRoute, "10.243.255.0/24", "k8s-node-route", false}, {"10.244.0.0/14", myClusterRoute, "10.243.255.0/24", false},
} }
for i, testCase := range testCases { for i, testCase := range testCases {
_, cidr, err := net.ParseCIDR(testCase.clusterCIDR) _, cidr, err := net.ParseCIDR(testCase.clusterCIDR)
@ -65,7 +58,6 @@ func TestIsResponsibleForRoute(t *testing.T) {
Name: testCase.routeName, Name: testCase.routeName,
TargetInstance: "doesnt-matter-for-this-test", TargetInstance: "doesnt-matter-for-this-test",
DestinationCIDR: testCase.routeCIDR, DestinationCIDR: testCase.routeCIDR,
Description: testCase.routeDescription,
} }
if resp := rc.isResponsibleForRoute(route); resp != testCase.expectedResponsible { if resp := rc.isResponsibleForRoute(route); resp != testCase.expectedResponsible {
t.Errorf("%d. isResponsibleForRoute() = %t; want %t", i, resp, testCase.expectedResponsible) t.Errorf("%d. isResponsibleForRoute() = %t; want %t", i, resp, testCase.expectedResponsible)
@ -87,12 +79,12 @@ func TestReconcile(t *testing.T) {
{ObjectMeta: api.ObjectMeta{Name: "node-2", UID: "02"}, Spec: api.NodeSpec{PodCIDR: "10.120.1.0/24"}}, {ObjectMeta: api.ObjectMeta{Name: "node-2", UID: "02"}, Spec: api.NodeSpec{PodCIDR: "10.120.1.0/24"}},
}, },
initialRoutes: []*cloudprovider.Route{ initialRoutes: []*cloudprovider.Route{
{cluster + "-01", "node-1", "10.120.0.0/24", "k8s-node-route"}, {cluster + "-01", "node-1", "10.120.0.0/24"},
{cluster + "-02", "node-2", "10.120.1.0/24", "k8s-node-route"}, {cluster + "-02", "node-2", "10.120.1.0/24"},
}, },
expectedRoutes: []*cloudprovider.Route{ expectedRoutes: []*cloudprovider.Route{
{cluster + "-01", "node-1", "10.120.0.0/24", "k8s-node-route"}, {cluster + "-01", "node-1", "10.120.0.0/24"},
{cluster + "-02", "node-2", "10.120.1.0/24", "k8s-node-route"}, {cluster + "-02", "node-2", "10.120.1.0/24"},
}, },
}, },
// 2 nodes, one route already there // 2 nodes, one route already there
@ -102,11 +94,11 @@ func TestReconcile(t *testing.T) {
{ObjectMeta: api.ObjectMeta{Name: "node-2", UID: "02"}, Spec: api.NodeSpec{PodCIDR: "10.120.1.0/24"}}, {ObjectMeta: api.ObjectMeta{Name: "node-2", UID: "02"}, Spec: api.NodeSpec{PodCIDR: "10.120.1.0/24"}},
}, },
initialRoutes: []*cloudprovider.Route{ initialRoutes: []*cloudprovider.Route{
{cluster + "-01", "node-1", "10.120.0.0/24", "k8s-node-route"}, {cluster + "-01", "node-1", "10.120.0.0/24"},
}, },
expectedRoutes: []*cloudprovider.Route{ expectedRoutes: []*cloudprovider.Route{
{cluster + "-01", "node-1", "10.120.0.0/24", "k8s-node-route"}, {cluster + "-01", "node-1", "10.120.0.0/24"},
{cluster + "-02", "node-2", "10.120.1.0/24", "k8s-node-route"}, {cluster + "-02", "node-2", "10.120.1.0/24"},
}, },
}, },
// 2 nodes, no routes yet // 2 nodes, no routes yet
@ -117,8 +109,8 @@ func TestReconcile(t *testing.T) {
}, },
initialRoutes: []*cloudprovider.Route{}, initialRoutes: []*cloudprovider.Route{},
expectedRoutes: []*cloudprovider.Route{ expectedRoutes: []*cloudprovider.Route{
{cluster + "-01", "node-1", "10.120.0.0/24", "k8s-node-route"}, {cluster + "-01", "node-1", "10.120.0.0/24"},
{cluster + "-02", "node-2", "10.120.1.0/24", "k8s-node-route"}, {cluster + "-02", "node-2", "10.120.1.0/24"},
}, },
}, },
// 2 nodes, a few too many routes // 2 nodes, a few too many routes
@ -128,14 +120,14 @@ func TestReconcile(t *testing.T) {
{ObjectMeta: api.ObjectMeta{Name: "node-2", UID: "02"}, Spec: api.NodeSpec{PodCIDR: "10.120.1.0/24"}}, {ObjectMeta: api.ObjectMeta{Name: "node-2", UID: "02"}, Spec: api.NodeSpec{PodCIDR: "10.120.1.0/24"}},
}, },
initialRoutes: []*cloudprovider.Route{ initialRoutes: []*cloudprovider.Route{
{cluster + "-01", "node-1", "10.120.0.0/24", "k8s-node-route"}, {cluster + "-01", "node-1", "10.120.0.0/24"},
{cluster + "-02", "node-2", "10.120.1.0/24", "k8s-node-route"}, {cluster + "-02", "node-2", "10.120.1.0/24"},
{cluster + "-03", "node-3", "10.120.2.0/24", "k8s-node-route"}, {cluster + "-03", "node-3", "10.120.2.0/24"},
{cluster + "-04", "node-4", "10.120.3.0/24", "k8s-node-route"}, {cluster + "-04", "node-4", "10.120.3.0/24"},
}, },
expectedRoutes: []*cloudprovider.Route{ expectedRoutes: []*cloudprovider.Route{
{cluster + "-01", "node-1", "10.120.0.0/24", "k8s-node-route"}, {cluster + "-01", "node-1", "10.120.0.0/24"},
{cluster + "-02", "node-2", "10.120.1.0/24", "k8s-node-route"}, {cluster + "-02", "node-2", "10.120.1.0/24"},
}, },
}, },
// 2 nodes, 2 routes, but only 1 is right // 2 nodes, 2 routes, but only 1 is right
@ -145,19 +137,22 @@ func TestReconcile(t *testing.T) {
{ObjectMeta: api.ObjectMeta{Name: "node-2", UID: "02"}, Spec: api.NodeSpec{PodCIDR: "10.120.1.0/24"}}, {ObjectMeta: api.ObjectMeta{Name: "node-2", UID: "02"}, Spec: api.NodeSpec{PodCIDR: "10.120.1.0/24"}},
}, },
initialRoutes: []*cloudprovider.Route{ initialRoutes: []*cloudprovider.Route{
{cluster + "-01", "node-1", "10.120.0.0/24", "k8s-node-route"}, {cluster + "-01", "node-1", "10.120.0.0/24"},
{cluster + "-03", "node-3", "10.120.2.0/24", "k8s-node-route"}, {cluster + "-03", "node-3", "10.120.2.0/24"},
}, },
expectedRoutes: []*cloudprovider.Route{ expectedRoutes: []*cloudprovider.Route{
{cluster + "-01", "node-1", "10.120.0.0/24", "k8s-node-route"}, {cluster + "-01", "node-1", "10.120.0.0/24"},
{cluster + "-02", "node-2", "10.120.1.0/24", "k8s-node-route"}, {cluster + "-02", "node-2", "10.120.1.0/24"},
}, },
}, },
} }
for i, testCase := range testCases { for i, testCase := range testCases {
cloud := &fake_cloud.FakeCloud{RouteMap: make(map[string]*cloudprovider.Route)} cloud := &fake_cloud.FakeCloud{RouteMap: make(map[string]*fake_cloud.FakeRoute)}
for _, route := range testCase.initialRoutes { for _, route := range testCase.initialRoutes {
cloud.RouteMap[route.Name] = route fakeRoute := &fake_cloud.FakeRoute{}
fakeRoute.ClusterName = cluster
fakeRoute.Route = *route
cloud.RouteMap[route.Name] = fakeRoute
} }
routes, ok := cloud.Routes() routes, ok := cloud.Routes()
if !ok { if !ok {
@ -177,7 +172,7 @@ func TestReconcile(t *testing.T) {
for { for {
select { select {
case <-tick.C: case <-tick.C:
if finalRoutes, err = routes.ListRoutes(""); err == nil && routeListEqual(finalRoutes, testCase.expectedRoutes) { if finalRoutes, err = routes.ListRoutes(cluster); err == nil && routeListEqual(finalRoutes, testCase.expectedRoutes) {
break poll break poll
} }
case <-timeoutChan: case <-timeoutChan: