Merge pull request #50467 from freehan/local-cloud-provider

Automatic merge from submit-queue (batch tested with PRs 51039, 50512, 50546, 50965, 50467)

add alpha api gate at gce cloud provider

**What this PR does / why we need it**:
Add a flag in gce.conf to gate alpha api. Related wrapper function can choose to examine corresponding gate. 
Currently, there is no new alpha api wrapper funciton being introduced. So there is no supported alpha api.


**Release note**:
```release-note
None
```

cc: @yujuhong @saad-ali @MrHohn
pull/6/head
Kubernetes Submit Queue 2017-08-22 21:16:16 -07:00 committed by GitHub
commit e2de110e26
4 changed files with 274 additions and 109 deletions

View File

@ -13,6 +13,7 @@ go_library(
"gce.go",
"gce_addresses.go",
"gce_addresses_fakes.go",
"gce_alpha.go",
"gce_annotations.go",
"gce_backendservice.go",
"gce_cert.go",

View File

@ -118,6 +118,11 @@ type GCECloud struct {
// lock to prevent shared resources from being prematurely deleted while the operation is
// in progress.
sharedResourceLock sync.Mutex
// AlphaFeatureGate gates gce alpha features in GCECloud instance.
// Related wrapper functions that interacts with gce alpha api should examine whether
// the corresponding api is enabled.
// If not enabled, it should return error.
AlphaFeatureGate *AlphaFeatureGate
}
type ServiceManager interface {
@ -151,6 +156,9 @@ type ConfigFile struct {
// Specifying ApiEndpoint will override the default GCE compute API endpoint.
ApiEndpoint string `gcfg:"api-endpoint"`
LocalZone string `gcfg:"local-zone"`
// Possible values: List of api names separated by comma. Default to none.
// For example: MyFeatureFlag
AlphaFeatures []string `gcfg:"alpha-features"`
}
}
@ -167,6 +175,7 @@ type CloudConfig struct {
NodeInstancePrefix string
TokenSource oauth2.TokenSource
UseMetadataServer bool
AlphaFeatureGate *AlphaFeatureGate
}
func init() {
@ -245,6 +254,12 @@ func generateCloudConfig(configFile *ConfigFile) (cloudConfig *CloudConfig, err
cloudConfig.NodeTags = configFile.Global.NodeTags
cloudConfig.NodeInstancePrefix = configFile.Global.NodeInstancePrefix
alphaFeatureGate, err := NewAlphaFeatureGate(configFile.Global.AlphaFeatures)
if err != nil {
glog.Errorf("Encountered error for creating alpha feature gate: %v", err)
}
cloudConfig.AlphaFeatureGate = alphaFeatureGate
}
// retrieve projectID and zone
@ -398,6 +413,7 @@ func CreateGCECloud(config *CloudConfig) (*GCECloud, error) {
nodeInstancePrefix: config.NodeInstancePrefix,
useMetadataServer: config.UseMetadataServer,
operationPollRateLimiter: operationPollRateLimiter,
AlphaFeatureGate: config.AlphaFeatureGate,
}
gce.manager = &GCEServiceManager{gce}

View File

@ -0,0 +1,47 @@
/*
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 gce
import (
"fmt"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
)
// All known alpha features
var knownAlphaFeatures = map[string]bool{}
type AlphaFeatureGate struct {
features map[string]bool
}
func (af *AlphaFeatureGate) Enabled(key string) bool {
return af.features[key]
}
func NewAlphaFeatureGate(features []string) (*AlphaFeatureGate, error) {
errList := []error{}
featureMap := make(map[string]bool)
for _, name := range features {
if _, ok := knownAlphaFeatures[name]; !ok {
errList = append(errList, fmt.Errorf("alpha feature %q is not supported.", name))
} else {
featureMap[name] = true
}
}
return &AlphaFeatureGate{featureMap}, utilerrors.NewAggregate(errList)
}

View File

@ -262,6 +262,36 @@ func TestSplitProviderID(t *testing.T) {
}
}
type generateConfigParams struct {
TokenURL string
TokenBody string
ProjectID string
NetworkName string
SubnetworkName string
NodeTags []string
NodeInstancePrefix string
Multizone bool
ApiEndpoint string
LocalZone string
AlphaFeatures []string
}
func newGenerateConfigDefaults() *generateConfigParams {
return &generateConfigParams{
TokenURL: "",
TokenBody: "",
ProjectID: "project-id",
NetworkName: "network-name",
SubnetworkName: "",
NodeTags: []string{"node-tag"},
NodeInstancePrefix: "node-prefix",
Multizone: false,
ApiEndpoint: "",
LocalZone: "us-central1-a",
AlphaFeatures: []string{},
}
}
func TestGenerateCloudConfigs(t *testing.T) {
testCases := []struct {
TokenURL string
@ -275,96 +305,10 @@ func TestGenerateCloudConfigs(t *testing.T) {
ApiEndpoint string
LocalZone string
cloudConfig *CloudConfig
AlphaFeatures []string
}{
// default config
{
TokenURL: "",
TokenBody: "",
ProjectID: "project-id",
NetworkName: "network-name",
SubnetworkName: "subnetwork-name",
NodeTags: []string{"node-tag"},
NodeInstancePrefix: "node-prefix",
Multizone: false,
ApiEndpoint: "",
LocalZone: "us-central1-a",
cloudConfig: &CloudConfig{
ApiEndpoint: "",
ProjectID: "project-id",
Region: "us-central1",
Zone: "us-central1-a",
ManagedZones: []string{"us-central1-a"},
NetworkURL: "https://www.googleapis.com/compute/v1/projects/project-id/global/networks/network-name",
SubnetworkURL: "https://www.googleapis.com/compute/v1/projects/project-id/regions/us-central1/subnetworks/subnetwork-name",
NodeTags: []string{"node-tag"},
NodeInstancePrefix: "node-prefix",
TokenSource: google.ComputeTokenSource(""),
UseMetadataServer: true,
},
},
// nil token source
{
TokenURL: "nil",
TokenBody: "",
ProjectID: "project-id",
NetworkName: "network-name",
SubnetworkName: "subnetwork-name",
NodeTags: []string{"node-tag"},
NodeInstancePrefix: "node-prefix",
Multizone: false,
ApiEndpoint: "",
LocalZone: "us-central1-a",
cloudConfig: &CloudConfig{
ApiEndpoint: "",
ProjectID: "project-id",
Region: "us-central1",
Zone: "us-central1-a",
ManagedZones: []string{"us-central1-a"},
NetworkURL: "https://www.googleapis.com/compute/v1/projects/project-id/global/networks/network-name",
SubnetworkURL: "https://www.googleapis.com/compute/v1/projects/project-id/regions/us-central1/subnetworks/subnetwork-name",
NodeTags: []string{"node-tag"},
NodeInstancePrefix: "node-prefix",
TokenSource: nil,
UseMetadataServer: true,
},
},
// specified api endpoint
{
TokenURL: "",
TokenBody: "",
ProjectID: "project-id",
NetworkName: "network-name",
SubnetworkName: "subnetwork-name",
NodeTags: []string{"node-tag"},
NodeInstancePrefix: "node-prefix",
Multizone: false,
ApiEndpoint: "https://www.googleapis.com/compute/staging_v1/",
LocalZone: "us-central1-a",
cloudConfig: &CloudConfig{
ApiEndpoint: "https://www.googleapis.com/compute/staging_v1/",
ProjectID: "project-id",
Region: "us-central1",
Zone: "us-central1-a",
ManagedZones: []string{"us-central1-a"},
NetworkURL: "https://www.googleapis.com/compute/staging_v1/projects/project-id/global/networks/network-name",
SubnetworkURL: "https://www.googleapis.com/compute/staging_v1/projects/project-id/regions/us-central1/subnetworks/subnetwork-name",
NodeTags: []string{"node-tag"},
NodeInstancePrefix: "node-prefix",
TokenSource: google.ComputeTokenSource(""),
UseMetadataServer: true,
},
},
// empty subnet-name
{
TokenURL: "",
TokenBody: "",
ProjectID: "project-id",
NetworkName: "network-name",
SubnetworkName: "",
NodeTags: []string{"node-tag"},
NodeInstancePrefix: "node-prefix",
Multizone: false,
ApiEndpoint: "",
LocalZone: "us-central1-a",
cloudConfig: &CloudConfig{
ApiEndpoint: "",
ProjectID: "project-id",
@ -377,20 +321,84 @@ func TestGenerateCloudConfigs(t *testing.T) {
NodeInstancePrefix: "node-prefix",
TokenSource: google.ComputeTokenSource(""),
UseMetadataServer: true,
AlphaFeatureGate: &AlphaFeatureGate{map[string]bool{}},
},
},
// nil token source
{
TokenURL: "nil",
cloudConfig: &CloudConfig{
ApiEndpoint: "",
ProjectID: "project-id",
Region: "us-central1",
Zone: "us-central1-a",
ManagedZones: []string{"us-central1-a"},
NetworkURL: "https://www.googleapis.com/compute/v1/projects/project-id/global/networks/network-name",
SubnetworkURL: "",
NodeTags: []string{"node-tag"},
NodeInstancePrefix: "node-prefix",
TokenSource: nil,
UseMetadataServer: true,
AlphaFeatureGate: &AlphaFeatureGate{map[string]bool{}},
},
},
// specified api endpoint
{
ApiEndpoint: "https://www.googleapis.com/compute/staging_v1/",
cloudConfig: &CloudConfig{
ApiEndpoint: "https://www.googleapis.com/compute/staging_v1/",
ProjectID: "project-id",
Region: "us-central1",
Zone: "us-central1-a",
ManagedZones: []string{"us-central1-a"},
NetworkURL: "https://www.googleapis.com/compute/staging_v1/projects/project-id/global/networks/network-name",
SubnetworkURL: "",
NodeTags: []string{"node-tag"},
NodeInstancePrefix: "node-prefix",
TokenSource: google.ComputeTokenSource(""),
UseMetadataServer: true,
AlphaFeatureGate: &AlphaFeatureGate{map[string]bool{}},
},
},
// fqdn subnetname
{
SubnetworkName: "https://www.googleapis.com/compute/v1/projects/project-id/regions/us-central1/subnetworks/subnetwork-name",
cloudConfig: &CloudConfig{
ApiEndpoint: "",
ProjectID: "project-id",
Region: "us-central1",
Zone: "us-central1-a",
ManagedZones: []string{"us-central1-a"},
NetworkURL: "https://www.googleapis.com/compute/v1/projects/project-id/global/networks/network-name",
SubnetworkURL: "https://www.googleapis.com/compute/v1/projects/project-id/regions/us-central1/subnetworks/subnetwork-name",
NodeTags: []string{"node-tag"},
NodeInstancePrefix: "node-prefix",
TokenSource: google.ComputeTokenSource(""),
UseMetadataServer: true,
AlphaFeatureGate: &AlphaFeatureGate{map[string]bool{}},
},
},
// subnetname
{
SubnetworkName: "subnetwork-name",
cloudConfig: &CloudConfig{
ApiEndpoint: "",
ProjectID: "project-id",
Region: "us-central1",
Zone: "us-central1-a",
ManagedZones: []string{"us-central1-a"},
NetworkURL: "https://www.googleapis.com/compute/v1/projects/project-id/global/networks/network-name",
SubnetworkURL: "https://www.googleapis.com/compute/v1/projects/project-id/regions/us-central1/subnetworks/subnetwork-name",
NodeTags: []string{"node-tag"},
NodeInstancePrefix: "node-prefix",
TokenSource: google.ComputeTokenSource(""),
UseMetadataServer: true,
AlphaFeatureGate: &AlphaFeatureGate{map[string]bool{}},
},
},
// multi zone
{
TokenURL: "",
TokenBody: "",
ProjectID: "project-id",
NetworkName: "network-name",
SubnetworkName: "subnetwork-name",
NodeTags: []string{"node-tag"},
NodeInstancePrefix: "node-prefix",
Multizone: true,
ApiEndpoint: "",
LocalZone: "us-central1-a",
cloudConfig: &CloudConfig{
ApiEndpoint: "",
ProjectID: "project-id",
@ -398,16 +406,45 @@ func TestGenerateCloudConfigs(t *testing.T) {
Zone: "us-central1-a",
ManagedZones: nil,
NetworkURL: "https://www.googleapis.com/compute/v1/projects/project-id/global/networks/network-name",
SubnetworkURL: "https://www.googleapis.com/compute/v1/projects/project-id/regions/us-central1/subnetworks/subnetwork-name",
SubnetworkURL: "",
NodeTags: []string{"node-tag"},
NodeInstancePrefix: "node-prefix",
TokenSource: google.ComputeTokenSource(""),
UseMetadataServer: true,
AlphaFeatureGate: &AlphaFeatureGate{map[string]bool{}},
},
},
}
for _, tc := range testCases {
config := newGenerateConfigDefaults()
config.Multizone = tc.Multizone
config.ApiEndpoint = tc.ApiEndpoint
config.AlphaFeatures = tc.AlphaFeatures
config.TokenBody = tc.TokenBody
if tc.TokenURL != "" {
config.TokenURL = tc.TokenURL
}
if tc.ProjectID != "" {
config.ProjectID = tc.ProjectID
}
if tc.NetworkName != "" {
config.NetworkName = tc.NetworkName
}
if tc.SubnetworkName != "" {
config.SubnetworkName = tc.SubnetworkName
}
if len(tc.NodeTags) > 0 {
config.NodeTags = tc.NodeTags
}
if tc.NodeInstancePrefix != "" {
config.NodeInstancePrefix = tc.NodeInstancePrefix
}
if tc.LocalZone != "" {
config.LocalZone = tc.LocalZone
}
cloudConfig, err := generateCloudConfig(&ConfigFile{
Global: struct {
TokenURL string `gcfg:"token-url"`
@ -420,17 +457,19 @@ func TestGenerateCloudConfigs(t *testing.T) {
Multizone bool `gcfg:"multizone"`
ApiEndpoint string `gcfg:"api-endpoint"`
LocalZone string `gcfg:"local-zone"`
AlphaFeatures []string `gcfg:"alpha-features"`
}{
TokenURL: tc.TokenURL,
TokenBody: tc.TokenBody,
ProjectID: tc.ProjectID,
NetworkName: tc.NetworkName,
SubnetworkName: tc.SubnetworkName,
NodeTags: tc.NodeTags,
NodeInstancePrefix: tc.NodeInstancePrefix,
Multizone: tc.Multizone,
ApiEndpoint: tc.ApiEndpoint,
LocalZone: tc.LocalZone,
TokenURL: config.TokenURL,
TokenBody: config.TokenBody,
ProjectID: config.ProjectID,
NetworkName: config.NetworkName,
SubnetworkName: config.SubnetworkName,
NodeTags: config.NodeTags,
NodeInstancePrefix: config.NodeInstancePrefix,
Multizone: config.Multizone,
ApiEndpoint: config.ApiEndpoint,
LocalZone: config.LocalZone,
AlphaFeatures: config.AlphaFeatures,
},
})
if err != nil {
@ -492,3 +531,65 @@ func getTestOperation() *computev1.Operation {
},
}
}
func TestNewAlphaFeatureGate(t *testing.T) {
knownAlphaFeatures["foo"] = true
knownAlphaFeatures["bar"] = true
testCases := []struct {
alphaFeatures []string
expectEnabled []string
expectDisabled []string
expectError bool
}{
// enable foo bar
{
alphaFeatures: []string{"foo", "bar"},
expectEnabled: []string{"foo", "bar"},
expectDisabled: []string{"aaa"},
expectError: false,
},
// no alpha feature
{
alphaFeatures: []string{},
expectEnabled: []string{},
expectDisabled: []string{"foo", "bar"},
expectError: false,
},
// unsupported alpha feature
{
alphaFeatures: []string{"aaa", "foo"},
expectError: true,
expectEnabled: []string{"foo"},
expectDisabled: []string{"aaa"},
},
// enable foo
{
alphaFeatures: []string{"foo"},
expectEnabled: []string{"foo"},
expectDisabled: []string{"bar"},
expectError: false,
},
}
for _, tc := range testCases {
featureGate, err := NewAlphaFeatureGate(tc.alphaFeatures)
if (tc.expectError && err == nil) || (!tc.expectError && err != nil) {
t.Errorf("Expect error to be %v, but got error %v", tc.expectError, err)
}
for _, key := range tc.expectEnabled {
if !featureGate.Enabled(key) {
t.Errorf("Expect %q to be enabled.", key)
}
}
for _, key := range tc.expectDisabled {
if featureGate.Enabled(key) {
t.Errorf("Expect %q to be disabled.", key)
}
}
}
delete(knownAlphaFeatures, "foo")
delete(knownAlphaFeatures, "bar")
}