Skip to content

Commit 6937763

Browse files
author
Kubernetes Submit Queue
authored
Merge pull request kubernetes#57351 from nicksardo/auto-sub
Automatic merge from submit-queue (batch tested with PRs 57351, 55654). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. GCE: Get automatically created subnetwork if none is specified for auto network Fixes kubernetes#57350 **Release note**: ```release-note GCE: Fixes ILB creation on automatic networks with manually created subnetworks. ```
2 parents 2ec792a + c893ce8 commit 6937763

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"
@@ -443,24 +444,26 @@ func CreateGCECloud(config *CloudConfig) (*GCECloud, error) {
443444
} else if config.SubnetworkName != "" {
444445
subnetURL = gceSubnetworkURL(config.ApiEndpoint, netProjID, config.Region, config.SubnetworkName)
445446
} else {
446-
// Attempt to determine the subnetwork in case it's an automatic network.
447-
// Legacy networks will not have a subnetwork, so subnetworkURL should remain empty.
447+
// Determine the type of network and attempt to discover the correct subnet for AUTO mode.
448+
// Gracefully fail because kubelet calls CreateGCECloud without any config, and minions
449+
// lack the proper credentials for API calls.
448450
if networkName := lastComponent(networkURL); networkName != "" {
449-
if n, err := getNetwork(service, netProjID, networkName); err != nil {
450-
// Gracefully fail because kubelet calls CreateGCECloud without any config, and API calls will fail coming from minions.
451-
glog.Warningf("Could not retrieve network %q in attempt to determine if legacy network or see list of subnets, err %v", networkURL, err)
451+
var n *compute.Network
452+
if n, err = getNetwork(service, netProjID, networkName); err != nil {
453+
glog.Warningf("Could not retrieve network %q; err: %v", networkName, err)
452454
} else {
453-
// Legacy networks have a non-empty IPv4Range
454-
if len(n.IPv4Range) > 0 {
455-
glog.Infof("Determined network %q is type legacy", networkURL)
455+
switch typeOfNetwork(n) {
456+
case netTypeLegacy:
457+
glog.Infof("Network %q is type legacy - no subnetwork", networkName)
456458
isLegacyNetwork = true
457-
} else {
458-
// Try to find the subnet in the list of subnets
459-
subnetURL = findSubnetForRegion(n.Subnetworks, config.Region)
460-
if len(subnetURL) > 0 {
461-
glog.Infof("Using subnet %q within network %q & region %q because none was specified.", subnetURL, n.Name, config.Region)
459+
case netTypeCustom:
460+
glog.Warningf("Network %q is type custom - cannot auto select a subnetwork", networkName)
461+
case netTypeAuto:
462+
subnetURL, err = determineSubnetURL(service, netProjID, networkName, config.Region)
463+
if err != nil {
464+
glog.Warningf("Could not determine subnetwork for network %q and region %v; err: %v", networkName, config.Region, err)
462465
} else {
463-
glog.Warningf("Could not find any subnet in region %q within list %v.", config.Region, n.Subnetworks)
466+
glog.Infof("Auto selecting subnetwork %q", subnetURL)
464467
}
465468
}
466469
}
@@ -507,6 +510,30 @@ func CreateGCECloud(config *CloudConfig) (*GCECloud, error) {
507510
return gce, nil
508511
}
509512

513+
// determineSubnetURL queries for all subnetworks in a region for a given network and returns
514+
// the URL of the subnetwork which exists in the auto-subnet range.
515+
func determineSubnetURL(service *compute.Service, networkProjectID, networkName, region string) (string, error) {
516+
subnets, err := listSubnetworksOfNetwork(service, networkProjectID, networkName, region)
517+
if err != nil {
518+
return "", err
519+
}
520+
521+
autoSubnets, err := subnetsInCIDR(subnets, autoSubnetIPRange)
522+
if err != nil {
523+
return "", err
524+
}
525+
526+
if len(autoSubnets) == 0 {
527+
return "", fmt.Errorf("no subnet exists in auto CIDR")
528+
}
529+
530+
if len(autoSubnets) > 1 {
531+
return "", fmt.Errorf("multiple subnetworks in the same region exist in auto CIDR")
532+
}
533+
534+
return autoSubnets[0].SelfLink, nil
535+
}
536+
510537
func tryConvertToProjectNames(configProject, configNetworkProject string, service *compute.Service) (projID, netProjID string) {
511538
projID = configProject
512539
if isProjectNumber(projID) {
@@ -734,6 +761,16 @@ func getNetwork(svc *compute.Service, networkProjectID, networkID string) (*comp
734761
return svc.Networks.Get(networkProjectID, networkID).Do()
735762
}
736763

764+
// listSubnetworksOfNetwork returns a list of subnetworks for a particular region of a network.
765+
func listSubnetworksOfNetwork(svc *compute.Service, networkProjectID, networkID, region string) ([]*compute.Subnetwork, error) {
766+
var subnets []*compute.Subnetwork
767+
err := svc.Subnetworks.List(networkProjectID, region).Filter(fmt.Sprintf("network eq .*/%v$", networkID)).Pages(context.Background(), func(res *compute.SubnetworkList) error {
768+
subnets = append(subnets, res.Items...)
769+
return nil
770+
})
771+
return subnets, err
772+
}
773+
737774
// getProjectID returns the project's string ID given a project number or string
738775
func getProjectID(svc *compute.Service, projectNumberOrID string) (string, error) {
739776
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)