Skip to content

Commit c893ce8

Browse files
author
Nick Sardo
committed
Get automatically created subnetwork if none is specified
1 parent eddb00e commit c893ce8

File tree

4 files changed

+205
-14
lines changed

4 files changed

+205
-14
lines changed

pkg/cloudprovider/providers/gce/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ go_test(
9595
"gce_healthchecks_test.go",
9696
"gce_loadbalancer_external_test.go",
9797
"gce_test.go",
98+
"gce_util_test.go",
9899
"metrics_test.go",
99100
],
100101
importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/gce",

pkg/cloudprovider/providers/gce/gce.go

+51-14
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package gce
1818

1919
import (
20+
"context"
2021
"fmt"
2122
"io"
2223
"net/http"
@@ -435,24 +436,26 @@ func CreateGCECloud(config *CloudConfig) (*GCECloud, error) {
435436
} else if config.SubnetworkName != "" {
436437
subnetURL = gceSubnetworkURL(config.ApiEndpoint, netProjID, config.Region, config.SubnetworkName)
437438
} else {
438-
// Attempt to determine the subnetwork in case it's an automatic network.
439-
// Legacy networks will not have a subnetwork, so subnetworkURL should remain empty.
439+
// Determine the type of network and attempt to discover the correct subnet for AUTO mode.
440+
// Gracefully fail because kubelet calls CreateGCECloud without any config, and minions
441+
// lack the proper credentials for API calls.
440442
if networkName := lastComponent(networkURL); networkName != "" {
441-
if n, err := getNetwork(service, netProjID, networkName); err != nil {
442-
// Gracefully fail because kubelet calls CreateGCECloud without any config, and API calls will fail coming from minions.
443-
glog.Warningf("Could not retrieve network %q in attempt to determine if legacy network or see list of subnets, err %v", networkURL, err)
443+
var n *compute.Network
444+
if n, err = getNetwork(service, netProjID, networkName); err != nil {
445+
glog.Warningf("Could not retrieve network %q; err: %v", networkName, err)
444446
} else {
445-
// Legacy networks have a non-empty IPv4Range
446-
if len(n.IPv4Range) > 0 {
447-
glog.Infof("Determined network %q is type legacy", networkURL)
447+
switch typeOfNetwork(n) {
448+
case netTypeLegacy:
449+
glog.Infof("Network %q is type legacy - no subnetwork", networkName)
448450
isLegacyNetwork = true
449-
} else {
450-
// Try to find the subnet in the list of subnets
451-
subnetURL = findSubnetForRegion(n.Subnetworks, config.Region)
452-
if len(subnetURL) > 0 {
453-
glog.Infof("Using subnet %q within network %q & region %q because none was specified.", subnetURL, n.Name, config.Region)
451+
case netTypeCustom:
452+
glog.Warningf("Network %q is type custom - cannot auto select a subnetwork", networkName)
453+
case netTypeAuto:
454+
subnetURL, err = determineSubnetURL(service, netProjID, networkName, config.Region)
455+
if err != nil {
456+
glog.Warningf("Could not determine subnetwork for network %q and region %v; err: %v", networkName, config.Region, err)
454457
} else {
455-
glog.Warningf("Could not find any subnet in region %q within list %v.", config.Region, n.Subnetworks)
458+
glog.Infof("Auto selecting subnetwork %q", subnetURL)
456459
}
457460
}
458461
}
@@ -499,6 +502,30 @@ func CreateGCECloud(config *CloudConfig) (*GCECloud, error) {
499502
return gce, nil
500503
}
501504

505+
// determineSubnetURL queries for all subnetworks in a region for a given network and returns
506+
// the URL of the subnetwork which exists in the auto-subnet range.
507+
func determineSubnetURL(service *compute.Service, networkProjectID, networkName, region string) (string, error) {
508+
subnets, err := listSubnetworksOfNetwork(service, networkProjectID, networkName, region)
509+
if err != nil {
510+
return "", err
511+
}
512+
513+
autoSubnets, err := subnetsInCIDR(subnets, autoSubnetIPRange)
514+
if err != nil {
515+
return "", err
516+
}
517+
518+
if len(autoSubnets) == 0 {
519+
return "", fmt.Errorf("no subnet exists in auto CIDR")
520+
}
521+
522+
if len(autoSubnets) > 1 {
523+
return "", fmt.Errorf("multiple subnetworks in the same region exist in auto CIDR")
524+
}
525+
526+
return autoSubnets[0].SelfLink, nil
527+
}
528+
502529
func tryConvertToProjectNames(configProject, configNetworkProject string, service *compute.Service) (projID, netProjID string) {
503530
projID = configProject
504531
if isProjectNumber(projID) {
@@ -740,6 +767,16 @@ func getNetwork(svc *compute.Service, networkProjectID, networkID string) (*comp
740767
return svc.Networks.Get(networkProjectID, networkID).Do()
741768
}
742769

770+
// listSubnetworksOfNetwork returns a list of subnetworks for a particular region of a network.
771+
func listSubnetworksOfNetwork(svc *compute.Service, networkProjectID, networkID, region string) ([]*compute.Subnetwork, error) {
772+
var subnets []*compute.Subnetwork
773+
err := svc.Subnetworks.List(networkProjectID, region).Filter(fmt.Sprintf("network eq .*/%v$", networkID)).Pages(context.Background(), func(res *compute.SubnetworkList) error {
774+
subnets = append(subnets, res.Items...)
775+
return nil
776+
})
777+
return subnets, err
778+
}
779+
743780
// getProjectID returns the project's string ID given a project number or string
744781
func getProjectID(svc *compute.Service, projectNumberOrID string) (string, error) {
745782
proj, err := svc.Projects.Get(projectNumberOrID).Do()

pkg/cloudprovider/providers/gce/gce_util.go

+63
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package gce
1919
import (
2020
"errors"
2121
"fmt"
22+
"net"
2223
"net/http"
2324
"regexp"
2425
"strings"
@@ -40,6 +41,13 @@ type gceInstance struct {
4041
Type string
4142
}
4243

44+
var (
45+
autoSubnetIPRange = &net.IPNet{
46+
IP: net.ParseIP("10.128.0.0"),
47+
Mask: net.CIDRMask(9, 32),
48+
}
49+
)
50+
4351
var providerIdRE = regexp.MustCompile(`^` + ProviderName + `://([^/]+)/([^/]+)/([^/]+)$`)
4452

4553
func getProjectAndZone() (string, string, error) {
@@ -211,3 +219,58 @@ func handleAlphaNetworkTierGetError(err error) (string, error) {
211219
// Can't get the network tier, just return an error.
212220
return "", err
213221
}
222+
223+
// containsCIDR returns true if outer contains inner.
224+
func containsCIDR(outer, inner *net.IPNet) bool {
225+
return outer.Contains(firstIPInRange(inner)) && outer.Contains(lastIPInRange(inner))
226+
}
227+
228+
// firstIPInRange returns the first IP in a given IP range.
229+
func firstIPInRange(ipNet *net.IPNet) net.IP {
230+
return ipNet.IP.Mask(ipNet.Mask)
231+
}
232+
233+
// lastIPInRange returns the last IP in a given IP range.
234+
func lastIPInRange(cidr *net.IPNet) net.IP {
235+
ip := append([]byte{}, cidr.IP...)
236+
for i, b := range cidr.Mask {
237+
ip[i] |= ^b
238+
}
239+
return ip
240+
}
241+
242+
// subnetsInCIDR takes a list of subnets for a single region and
243+
// returns subnets which exists in the specified CIDR range.
244+
func subnetsInCIDR(subnets []*compute.Subnetwork, cidr *net.IPNet) ([]*compute.Subnetwork, error) {
245+
var res []*compute.Subnetwork
246+
for _, subnet := range subnets {
247+
_, subnetRange, err := net.ParseCIDR(subnet.IpCidrRange)
248+
if err != nil {
249+
return nil, fmt.Errorf("unable to parse CIDR %q for subnet %q: %v", subnet.IpCidrRange, subnet.Name, err)
250+
}
251+
if containsCIDR(cidr, subnetRange) {
252+
res = append(res, subnet)
253+
}
254+
}
255+
return res, nil
256+
}
257+
258+
type netType string
259+
260+
const (
261+
netTypeLegacy netType = "LEGACY"
262+
netTypeAuto netType = "AUTO"
263+
netTypeCustom netType = "CUSTOM"
264+
)
265+
266+
func typeOfNetwork(network *compute.Network) netType {
267+
if network.IPv4Range != "" {
268+
return netTypeLegacy
269+
}
270+
271+
if network.AutoCreateSubnetworks {
272+
return netTypeAuto
273+
}
274+
275+
return netTypeCustom
276+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
Copyright 2017 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package gce
18+
19+
import (
20+
"net"
21+
"reflect"
22+
"testing"
23+
24+
compute "google.golang.org/api/compute/v1"
25+
)
26+
27+
func TestLastIPInRange(t *testing.T) {
28+
for _, tc := range []struct {
29+
cidr string
30+
want string
31+
}{
32+
{"10.1.2.3/32", "10.1.2.3"},
33+
{"10.1.2.0/31", "10.1.2.1"},
34+
{"10.1.0.0/30", "10.1.0.3"},
35+
{"10.0.0.0/29", "10.0.0.7"},
36+
{"::0/128", "::"},
37+
{"::0/127", "::1"},
38+
{"::0/126", "::3"},
39+
{"::0/120", "::ff"},
40+
} {
41+
_, c, err := net.ParseCIDR(tc.cidr)
42+
if err != nil {
43+
t.Errorf("net.ParseCIDR(%v) = _, %v, %v; want nil", tc.cidr, c, err)
44+
continue
45+
}
46+
47+
if lastIP := lastIPInRange(c); lastIP.String() != tc.want {
48+
t.Errorf("LastIPInRange(%v) = %v; want %v", tc.cidr, lastIP, tc.want)
49+
}
50+
}
51+
}
52+
53+
func TestSubnetsInCIDR(t *testing.T) {
54+
subnets := []*compute.Subnetwork{
55+
{
56+
Name: "A",
57+
IpCidrRange: "10.0.0.0/20",
58+
},
59+
{
60+
Name: "B",
61+
IpCidrRange: "10.0.16.0/20",
62+
},
63+
{
64+
Name: "C",
65+
IpCidrRange: "10.132.0.0/20",
66+
},
67+
{
68+
Name: "D",
69+
IpCidrRange: "10.0.32.0/20",
70+
},
71+
{
72+
Name: "E",
73+
IpCidrRange: "10.134.0.0/20",
74+
},
75+
}
76+
expectedNames := []string{"C", "E"}
77+
78+
gotSubs, err := subnetsInCIDR(subnets, autoSubnetIPRange)
79+
if err != nil {
80+
t.Errorf("autoSubnetInList() = _, %v", err)
81+
}
82+
83+
var gotNames []string
84+
for _, v := range gotSubs {
85+
gotNames = append(gotNames, v.Name)
86+
}
87+
if !reflect.DeepEqual(gotNames, expectedNames) {
88+
t.Errorf("autoSubnetInList() = %v, expected: %v", gotNames, expectedNames)
89+
}
90+
}

0 commit comments

Comments
 (0)