mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-31 15:25:57 +00:00
Merge pull request #57879 from bowei/gce-gen
Automatic merge from submit-queue (batch tested with PRs 58025, 57112, 57879, 57571, 58062). 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>. Code generation for GCE compute interface Use code generation to "write" most of the GCE cloud provider library. This enables the following: - Consistent interfaces, including handling of the different API versions (GA, alpha, beta) - Efficient implementation of cross cutting features such as metrics, logging, tracing etc. Adding such features has in the past been a tedious and error prone endeavor. - High fidelity mocks for all of the compute API. What this means is that most of our controller logic can be tested as unit tests in a consistent way without creating individual mocks by hand. ```release-note NONE ```
This commit is contained in:
commit
b873fc4453
@ -87,6 +87,7 @@ pkg/cloudprovider
|
||||
pkg/cloudprovider/providers/aws
|
||||
pkg/cloudprovider/providers/fake
|
||||
pkg/cloudprovider/providers/gce
|
||||
pkg/cloudprovider/providers/gce/cloud
|
||||
pkg/cloudprovider/providers/openstack
|
||||
pkg/cloudprovider/providers/ovirt
|
||||
pkg/cloudprovider/providers/photon
|
||||
|
38
hack/update-cloudprovider-gce.sh
Executable file
38
hack/update-cloudprovider-gce.sh
Executable file
@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2018 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.
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
|
||||
source "${KUBE_ROOT}/hack/lib/init.sh"
|
||||
GENERATOR="${KUBE_ROOT}/pkg/cloudprovider/providers/gce/cloud/gen/main.go"
|
||||
|
||||
GEN_GO="${KUBE_ROOT}/pkg/cloudprovider/providers/gce/cloud/gen.go"
|
||||
GEN_TEST_GO="${KUBE_ROOT}/pkg/cloudprovider/providers/gce/cloud/gen_test.go"
|
||||
|
||||
kube::golang::setup_env
|
||||
|
||||
TMPFILE=$(mktemp verify-cloudprovider-gce-XXXX)
|
||||
trap "{ rm -f ${TMPFILE}; }" EXIT
|
||||
|
||||
go run "${GENERATOR}" > ${TMPFILE}
|
||||
mv "${TMPFILE}" "${GEN_GO}"
|
||||
go run "${GENERATOR}" -mode test > ${TMPFILE}
|
||||
mv "${TMPFILE}" "${GEN_TEST_GO}"
|
||||
|
||||
exit 0
|
49
hack/verify-cloudprovider-gce.sh
Executable file
49
hack/verify-cloudprovider-gce.sh
Executable file
@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2018 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.
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
|
||||
source "${KUBE_ROOT}/hack/lib/init.sh"
|
||||
GENERATOR="${KUBE_ROOT}/pkg/cloudprovider/providers/gce/cloud/gen/main.go"
|
||||
|
||||
GEN_GO="${KUBE_ROOT}/pkg/cloudprovider/providers/gce/cloud/gen.go"
|
||||
GEN_TEST_GO="${KUBE_ROOT}/pkg/cloudprovider/providers/gce/cloud/gen_test.go"
|
||||
|
||||
kube::golang::setup_env
|
||||
|
||||
TMPFILE=$(mktemp verify-cloudprovider-gce-XXXX)
|
||||
trap "{ rm -f ${TMPFILE}; }" EXIT
|
||||
|
||||
go run "${GENERATOR}" > ${TMPFILE}
|
||||
if ! diff "${TMPFILE}" "${GEN_GO}"; then
|
||||
echo "Generated file ${GEN_GO} needs to be updated (run hack/update-cloudprovider-gce.sh)"
|
||||
echo
|
||||
diff -u "${TMPFILE}" "${GEN_GO}" || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
go run "${GENERATOR}" -mode test > ${TMPFILE}
|
||||
if ! diff "${TMPFILE}" "${GEN_TEST_GO}"; then
|
||||
echo "Generated file ${GEN_TEST_GO} needs to be updated (run hack/update-cloudprovider-gce.sh)"
|
||||
echo
|
||||
diff -u "${TMPFILE}" "${GEN_TEST_GO}" || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
@ -125,6 +125,9 @@ filegroup(
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/cloudprovider/providers/gce/cloud:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
63
pkg/cloudprovider/providers/gce/cloud/BUILD
Normal file
63
pkg/cloudprovider/providers/gce/cloud/BUILD
Normal file
@ -0,0 +1,63 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"doc.go",
|
||||
"gce_projects.go",
|
||||
"gen.go",
|
||||
"op.go",
|
||||
"project.go",
|
||||
"ratelimit.go",
|
||||
"service.go",
|
||||
"utils.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/cloudprovider/providers/gce/cloud/filter:go_default_library",
|
||||
"//pkg/cloudprovider/providers/gce/cloud/meta:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/google.golang.org/api/compute/v0.alpha:go_default_library",
|
||||
"//vendor/google.golang.org/api/compute/v0.beta:go_default_library",
|
||||
"//vendor/google.golang.org/api/compute/v1:go_default_library",
|
||||
"//vendor/google.golang.org/api/googleapi:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"gen_test.go",
|
||||
"mock_test.go",
|
||||
"utils_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud",
|
||||
deps = [
|
||||
"//pkg/cloudprovider/providers/gce/cloud/filter:go_default_library",
|
||||
"//pkg/cloudprovider/providers/gce/cloud/meta:go_default_library",
|
||||
"//vendor/google.golang.org/api/compute/v0.alpha:go_default_library",
|
||||
"//vendor/google.golang.org/api/compute/v0.beta:go_default_library",
|
||||
"//vendor/google.golang.org/api/compute/v1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/cloudprovider/providers/gce/cloud/filter:all-srcs",
|
||||
"//pkg/cloudprovider/providers/gce/cloud/gen:all-srcs",
|
||||
"//pkg/cloudprovider/providers/gce/cloud/meta:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
112
pkg/cloudprovider/providers/gce/cloud/doc.go
Normal file
112
pkg/cloudprovider/providers/gce/cloud/doc.go
Normal file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
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 cloud implements a more golang friendly interface to the GCE compute
|
||||
// API. The code in this package is generated automatically via the generator
|
||||
// implemented in "gen/main.go". The code generator creates the basic CRUD
|
||||
// actions for the given resource: "Insert", "Get", "List" and "Delete".
|
||||
// Additional methods by customizing the ServiceInfo object (see below).
|
||||
// Generated code includes a full mock of the GCE compute API.
|
||||
//
|
||||
// Usage
|
||||
//
|
||||
// The root of the GCE compute API is the interface "Cloud". Code written using
|
||||
// Cloud can be used against the actual implementation "GCE" or "MockGCE".
|
||||
//
|
||||
// func foo(cloud Cloud) {
|
||||
// igs, err := cloud.InstanceGroups().List(ctx, "us-central1-b", filter.None)
|
||||
// ...
|
||||
// }
|
||||
// // Run foo against the actual cloud.
|
||||
// foo(NewGCE(&Service{...}))
|
||||
// // Run foo with a mock.
|
||||
// foo(NewMockGCE())
|
||||
//
|
||||
// Rate limiting and routing
|
||||
//
|
||||
// The generated code allows for custom policies for operation rate limiting
|
||||
// and GCE project routing. See RateLimiter and ProjectRouter for more details.
|
||||
//
|
||||
// Mocks
|
||||
//
|
||||
// Mocks are automatically generated for each type implementing basic logic for
|
||||
// resource manipulation. This eliminates the boilerplate required to mock GCE
|
||||
// functionality. Each method will also have a corresponding "xxxHook"
|
||||
// function generated in the mock structure where unit test code can hook the
|
||||
// execution of the method.
|
||||
//
|
||||
// Mocks for different versions of the same service will share the same set of
|
||||
// objects, i.e. an alpha object will be visible with beta and GA methods.
|
||||
// Note that translation is done with JSON serialization between the API versions.
|
||||
//
|
||||
// Changing service code generation
|
||||
//
|
||||
// The list of services to generate is contained in "meta/meta.go". To add a
|
||||
// service, add an entry to the list "meta.AllServices". An example entry:
|
||||
//
|
||||
// &ServiceInfo{
|
||||
// Object: "InstanceGroup", // Name of the object type.
|
||||
// Service: "InstanceGroups", // Name of the service.
|
||||
// Resource: "instanceGroups", // Lowercase resource name (as appears in the URL).
|
||||
// version: meta.VersionAlpha, // API version (one entry per version is needed).
|
||||
// keyType: Zonal, // What kind of resource this is.
|
||||
// serviceType: reflect.TypeOf(&alpha.InstanceGroupsService{}), // Associated golang type.
|
||||
// additionalMethods: []string{ // Additional methods to generate code for.
|
||||
// "SetNamedPorts",
|
||||
// },
|
||||
// options: <options> // Or'd ("|") together.
|
||||
// }
|
||||
//
|
||||
// Read-only objects
|
||||
//
|
||||
// Services such as Regions and Zones do not allow for mutations. Specify
|
||||
// "ReadOnly" in ServiceInfo.options to omit the mutation methods.
|
||||
//
|
||||
// Adding custom methods
|
||||
//
|
||||
// Some methods that may not be properly handled by the generated code. To enable
|
||||
// addition of custom code to the generated mocks, set the "CustomOps" option
|
||||
// in "meta.ServiceInfo" entry. This will make the generated service interface
|
||||
// embed a "<ServiceName>Ops" interface. This interface MUST be written by hand
|
||||
// and contain the custom method logic. Corresponding methods must be added to
|
||||
// the corresponding Mockxxx and GCExxx struct types.
|
||||
//
|
||||
// // In "meta/meta.go":
|
||||
// &ServiceInfo{
|
||||
// Object: "InstanceGroup",
|
||||
// ...
|
||||
// options: CustomOps,
|
||||
// }
|
||||
//
|
||||
// // In the generated code "gen.go":
|
||||
// type InstanceGroups interface {
|
||||
// InstanceGroupsOps // Added by CustomOps option.
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// // In hand written file:
|
||||
// type InstanceGroupsOps interface {
|
||||
// MyMethod()
|
||||
// }
|
||||
//
|
||||
// func (mock *MockInstanceGroups) MyMethod() {
|
||||
// // Custom mock implementation.
|
||||
// }
|
||||
//
|
||||
// func (gce *GCEInstanceGroups) MyMethod() {
|
||||
// // Custom implementation.
|
||||
// }
|
||||
package cloud
|
30
pkg/cloudprovider/providers/gce/cloud/filter/BUILD
Normal file
30
pkg/cloudprovider/providers/gce/cloud/filter/BUILD
Normal file
@ -0,0 +1,30 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["filter.go"],
|
||||
importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/filter",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["//vendor/github.com/golang/glog:go_default_library"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["filter_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/filter",
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
303
pkg/cloudprovider/providers/gce/cloud/filter/filter.go
Normal file
303
pkg/cloudprovider/providers/gce/cloud/filter/filter.go
Normal file
@ -0,0 +1,303 @@
|
||||
/*
|
||||
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 filter encapsulates the filter argument to compute API calls.
|
||||
//
|
||||
// // List all global addresses (no filter).
|
||||
// c.GlobalAddresses().List(ctx, filter.None)
|
||||
//
|
||||
// // List global addresses filtering for name matching "abc.*".
|
||||
// c.GlobalAddresses().List(ctx, filter.Regexp("name", "abc.*"))
|
||||
//
|
||||
// // List on multiple conditions.
|
||||
// f := filter.Regexp("name", "homer.*").AndNotRegexp("name", "homers")
|
||||
// c.GlobalAddresses().List(ctx, f)
|
||||
package filter
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
var (
|
||||
// None indicates that the List result set should not be filter (i.e.
|
||||
// return all values).
|
||||
None *F
|
||||
)
|
||||
|
||||
// Regexp returns a filter for fieldName matches regexp v.
|
||||
func Regexp(fieldName, v string) *F {
|
||||
return (&F{}).AndRegexp(fieldName, v)
|
||||
}
|
||||
|
||||
// NotRegexp returns a filter for fieldName not matches regexp v.
|
||||
func NotRegexp(fieldName, v string) *F {
|
||||
return (&F{}).AndNotRegexp(fieldName, v)
|
||||
}
|
||||
|
||||
// EqualInt returns a filter for fieldName ~ v.
|
||||
func EqualInt(fieldName string, v int) *F {
|
||||
return (&F{}).AndEqualInt(fieldName, v)
|
||||
}
|
||||
|
||||
// NotEqualInt returns a filter for fieldName != v.
|
||||
func NotEqualInt(fieldName string, v int) *F {
|
||||
return (&F{}).AndNotEqualInt(fieldName, v)
|
||||
}
|
||||
|
||||
// EqualBool returns a filter for fieldName == v.
|
||||
func EqualBool(fieldName string, v bool) *F {
|
||||
return (&F{}).AndEqualBool(fieldName, v)
|
||||
}
|
||||
|
||||
// NotEqualBool returns a filter for fieldName != v.
|
||||
func NotEqualBool(fieldName string, v bool) *F {
|
||||
return (&F{}).AndNotEqualBool(fieldName, v)
|
||||
}
|
||||
|
||||
// F is a filter to be used with List() operations.
|
||||
//
|
||||
// From the compute API description:
|
||||
//
|
||||
// Sets a filter {expression} for filtering listed resources. Your {expression}
|
||||
// must be in the format: field_name comparison_string literal_string.
|
||||
//
|
||||
// The field_name is the name of the field you want to compare. Only atomic field
|
||||
// types are supported (string, number, boolean). The comparison_string must be
|
||||
// either eq (equals) or ne (not equals). The literal_string is the string value
|
||||
// to filter to. The literal value must be valid for the type of field you are
|
||||
// filtering by (string, number, boolean). For string fields, the literal value is
|
||||
// interpreted as a regular expression using RE2 syntax. The literal value must
|
||||
// match the entire field.
|
||||
//
|
||||
// For example, to filter for instances that do not have a name of
|
||||
// example-instance, you would use name ne example-instance.
|
||||
//
|
||||
// You can filter on nested fields. For example, you could filter on instances
|
||||
// that have set the scheduling.automaticRestart field to true. Use filtering on
|
||||
// nested fields to take advantage of labels to organize and search for results
|
||||
// based on label values.
|
||||
//
|
||||
// To filter on multiple expressions, provide each separate expression within
|
||||
// parentheses. For example, (scheduling.automaticRestart eq true)
|
||||
// (zone eq us-central1-f). Multiple expressions are treated as AND expressions,
|
||||
// meaning that resources must match all expressions to pass the filters.
|
||||
type F struct {
|
||||
predicates []filterPredicate
|
||||
}
|
||||
|
||||
// And joins two filters together.
|
||||
func (fl *F) And(rest *F) *F {
|
||||
fl.predicates = append(fl.predicates, rest.predicates...)
|
||||
return fl
|
||||
}
|
||||
|
||||
// AndRegexp adds a field match string predicate.
|
||||
func (fl *F) AndRegexp(fieldName, v string) *F {
|
||||
fl.predicates = append(fl.predicates, filterPredicate{fieldName: fieldName, op: equals, s: &v})
|
||||
return fl
|
||||
}
|
||||
|
||||
// AndNotRegexp adds a field not match string predicate.
|
||||
func (fl *F) AndNotRegexp(fieldName, v string) *F {
|
||||
fl.predicates = append(fl.predicates, filterPredicate{fieldName: fieldName, op: notEquals, s: &v})
|
||||
return fl
|
||||
}
|
||||
|
||||
// AndEqualInt adds a field == int predicate.
|
||||
func (fl *F) AndEqualInt(fieldName string, v int) *F {
|
||||
fl.predicates = append(fl.predicates, filterPredicate{fieldName: fieldName, op: equals, i: &v})
|
||||
return fl
|
||||
}
|
||||
|
||||
// AndNotEqualInt adds a field != int predicate.
|
||||
func (fl *F) AndNotEqualInt(fieldName string, v int) *F {
|
||||
fl.predicates = append(fl.predicates, filterPredicate{fieldName: fieldName, op: notEquals, i: &v})
|
||||
return fl
|
||||
}
|
||||
|
||||
// AndEqualBool adds a field == bool predicate.
|
||||
func (fl *F) AndEqualBool(fieldName string, v bool) *F {
|
||||
fl.predicates = append(fl.predicates, filterPredicate{fieldName: fieldName, op: equals, b: &v})
|
||||
return fl
|
||||
}
|
||||
|
||||
// AndNotEqualBool adds a field != bool predicate.
|
||||
func (fl *F) AndNotEqualBool(fieldName string, v bool) *F {
|
||||
fl.predicates = append(fl.predicates, filterPredicate{fieldName: fieldName, op: notEquals, b: &v})
|
||||
return fl
|
||||
}
|
||||
|
||||
func (fl *F) String() string {
|
||||
if len(fl.predicates) == 1 {
|
||||
return fl.predicates[0].String()
|
||||
}
|
||||
|
||||
var pl []string
|
||||
for _, p := range fl.predicates {
|
||||
pl = append(pl, "("+p.String()+")")
|
||||
}
|
||||
return strings.Join(pl, " ")
|
||||
}
|
||||
|
||||
// Match returns true if the F as specifies matches the given object. This
|
||||
// is used by the Mock implementations to perform filtering and SHOULD NOT be
|
||||
// used in production code as it is not well-tested to be equivalent to the
|
||||
// actual compute API.
|
||||
func (fl *F) Match(obj interface{}) bool {
|
||||
if fl == nil {
|
||||
return true
|
||||
}
|
||||
for _, p := range fl.predicates {
|
||||
if !p.match(obj) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type filterOp int
|
||||
|
||||
const (
|
||||
equals filterOp = iota
|
||||
notEquals filterOp = iota
|
||||
)
|
||||
|
||||
// filterPredicate is an individual predicate for a fieldName and value.
|
||||
type filterPredicate struct {
|
||||
fieldName string
|
||||
|
||||
op filterOp
|
||||
s *string
|
||||
i *int
|
||||
b *bool
|
||||
}
|
||||
|
||||
func (fp *filterPredicate) String() string {
|
||||
var op string
|
||||
switch fp.op {
|
||||
case equals:
|
||||
op = "eq"
|
||||
case notEquals:
|
||||
op = "ne"
|
||||
default:
|
||||
op = "invalidOp"
|
||||
}
|
||||
|
||||
var value string
|
||||
switch {
|
||||
case fp.s != nil:
|
||||
// There does not seem to be any sort of escaping as specified in the
|
||||
// document. This means it's possible to create malformed expressions.
|
||||
value = *fp.s
|
||||
case fp.i != nil:
|
||||
value = fmt.Sprintf("%d", *fp.i)
|
||||
case fp.b != nil:
|
||||
value = fmt.Sprintf("%t", *fp.b)
|
||||
default:
|
||||
value = "invalidValue"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s %s %s", fp.fieldName, op, value)
|
||||
}
|
||||
|
||||
func (fp *filterPredicate) match(o interface{}) bool {
|
||||
v, err := extractValue(fp.fieldName, o)
|
||||
glog.V(6).Infof("extractValue(%q, %#v) = %v, %v", fp.fieldName, o, v, err)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var match bool
|
||||
switch x := v.(type) {
|
||||
case string:
|
||||
if fp.s == nil {
|
||||
return false
|
||||
}
|
||||
re, err := regexp.Compile(*fp.s)
|
||||
if err != nil {
|
||||
glog.Errorf("Match regexp %q is invalid: %v", *fp.s, err)
|
||||
return false
|
||||
}
|
||||
match = re.Match([]byte(x))
|
||||
case int:
|
||||
if fp.i == nil {
|
||||
return false
|
||||
}
|
||||
match = x == *fp.i
|
||||
case bool:
|
||||
if fp.b == nil {
|
||||
return false
|
||||
}
|
||||
match = x == *fp.b
|
||||
}
|
||||
|
||||
switch fp.op {
|
||||
case equals:
|
||||
return match
|
||||
case notEquals:
|
||||
return !match
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// snakeToCamelCase converts from "names_like_this" to "NamesLikeThis" to
|
||||
// interoperate between proto and Golang naming conventions.
|
||||
func snakeToCamelCase(s string) string {
|
||||
parts := strings.Split(s, "_")
|
||||
var ret string
|
||||
for _, x := range parts {
|
||||
ret += strings.Title(x)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// extractValue returns the value of the field named by path in object o if it exists.
|
||||
func extractValue(path string, o interface{}) (interface{}, error) {
|
||||
parts := strings.Split(path, ".")
|
||||
for _, f := range parts {
|
||||
v := reflect.ValueOf(o)
|
||||
// Dereference Ptr to handle *struct.
|
||||
if v.Kind() == reflect.Ptr {
|
||||
if v.IsNil() {
|
||||
return nil, errors.New("field is nil")
|
||||
}
|
||||
v = v.Elem()
|
||||
}
|
||||
if v.Kind() != reflect.Struct {
|
||||
return nil, fmt.Errorf("cannot get field from non-struct (%T)", o)
|
||||
}
|
||||
v = v.FieldByName(snakeToCamelCase(f))
|
||||
if !v.IsValid() {
|
||||
return nil, fmt.Errorf("cannot get field %q as it is not a valid field in %T", f, o)
|
||||
}
|
||||
if !v.CanInterface() {
|
||||
return nil, fmt.Errorf("cannot get field %q in obj of type %T", f, o)
|
||||
}
|
||||
o = v.Interface()
|
||||
}
|
||||
switch o.(type) {
|
||||
case string, int, bool:
|
||||
return o, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unhandled object of type %T", o)
|
||||
}
|
176
pkg/cloudprovider/providers/gce/cloud/filter/filter_test.go
Normal file
176
pkg/cloudprovider/providers/gce/cloud/filter/filter_test.go
Normal file
@ -0,0 +1,176 @@
|
||||
/*
|
||||
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 filter
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFilterToString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range []struct {
|
||||
f *F
|
||||
want string
|
||||
}{
|
||||
{Regexp("field1", "abc"), `field1 eq abc`},
|
||||
{NotRegexp("field1", "abc"), `field1 ne abc`},
|
||||
{EqualInt("field1", 13), "field1 eq 13"},
|
||||
{NotEqualInt("field1", 13), "field1 ne 13"},
|
||||
{EqualBool("field1", true), "field1 eq true"},
|
||||
{NotEqualBool("field1", true), "field1 ne true"},
|
||||
{Regexp("field1", "abc").AndRegexp("field2", "def"), `(field1 eq abc) (field2 eq def)`},
|
||||
{Regexp("field1", "abc").AndNotEqualInt("field2", 17), `(field1 eq abc) (field2 ne 17)`},
|
||||
{Regexp("field1", "abc").And(EqualInt("field2", 17)), `(field1 eq abc) (field2 eq 17)`},
|
||||
} {
|
||||
if tc.f.String() != tc.want {
|
||||
t.Errorf("filter %#v String() = %q, want %q", tc.f, tc.f.String(), tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterMatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type inner struct {
|
||||
X string
|
||||
}
|
||||
type S struct {
|
||||
S string
|
||||
I int
|
||||
B bool
|
||||
Unhandled struct{}
|
||||
NestedField *inner
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
f *F
|
||||
o interface{}
|
||||
want bool
|
||||
}{
|
||||
{f: None, o: &S{}, want: true},
|
||||
{f: Regexp("s", "abc"), o: &S{}},
|
||||
{f: EqualInt("i", 10), o: &S{}},
|
||||
{f: EqualBool("b", true), o: &S{}},
|
||||
{f: NotRegexp("s", "abc"), o: &S{}, want: true},
|
||||
{f: NotEqualInt("i", 10), o: &S{}, want: true},
|
||||
{f: NotEqualBool("b", true), o: &S{}, want: true},
|
||||
{f: Regexp("s", "abc").AndEqualBool("b", true), o: &S{}},
|
||||
{f: Regexp("s", "abc"), o: &S{S: "abc"}, want: true},
|
||||
{f: Regexp("s", "a.*"), o: &S{S: "abc"}, want: true},
|
||||
{f: Regexp("s", "a((("), o: &S{S: "abc"}},
|
||||
{f: NotRegexp("s", "abc"), o: &S{S: "abc"}},
|
||||
{f: EqualInt("i", 10), o: &S{I: 11}},
|
||||
{f: EqualInt("i", 10), o: &S{I: 10}, want: true},
|
||||
{f: Regexp("s", "abc").AndEqualBool("b", true), o: &S{S: "abc"}},
|
||||
{f: Regexp("s", "abcd").AndEqualBool("b", true), o: &S{S: "abc"}},
|
||||
{f: Regexp("s", "abc").AndEqualBool("b", true), o: &S{S: "abc", B: true}, want: true},
|
||||
{f: Regexp("s", "abc").And(EqualBool("b", true)), o: &S{S: "abc", B: true}, want: true},
|
||||
{f: Regexp("unhandled", "xyz"), o: &S{}},
|
||||
{f: Regexp("nested_field.x", "xyz"), o: &S{}},
|
||||
{f: Regexp("nested_field.x", "xyz"), o: &S{NestedField: &inner{"xyz"}}, want: true},
|
||||
{f: NotRegexp("nested_field.x", "xyz"), o: &S{NestedField: &inner{"xyz"}}},
|
||||
{f: Regexp("nested_field.y", "xyz"), o: &S{NestedField: &inner{"xyz"}}},
|
||||
{f: Regexp("nested_field", "xyz"), o: &S{NestedField: &inner{"xyz"}}},
|
||||
} {
|
||||
got := tc.f.Match(tc.o)
|
||||
if got != tc.want {
|
||||
t.Errorf("%v: Match(%+v) = %v, want %v", tc.f, tc.o, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterSnakeToCamelCase(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range []struct {
|
||||
s string
|
||||
want string
|
||||
}{
|
||||
{"", ""},
|
||||
{"abc", "Abc"},
|
||||
{"_foo", "Foo"},
|
||||
{"a_b_c", "ABC"},
|
||||
{"a_BC_def", "ABCDef"},
|
||||
{"a_Bc_def", "ABcDef"},
|
||||
} {
|
||||
got := snakeToCamelCase(tc.s)
|
||||
if got != tc.want {
|
||||
t.Errorf("snakeToCamelCase(%q) = %q, want %q", tc.s, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterExtractValue(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type nest2 struct {
|
||||
Y string
|
||||
}
|
||||
type nest struct {
|
||||
X string
|
||||
Nest2 nest2
|
||||
}
|
||||
st := &struct {
|
||||
S string
|
||||
I int
|
||||
F bool
|
||||
Nest nest
|
||||
NestPtr *nest
|
||||
|
||||
Unhandled float64
|
||||
}{
|
||||
"abc",
|
||||
13,
|
||||
true,
|
||||
nest{"xyz", nest2{"zzz"}},
|
||||
&nest{"yyy", nest2{}},
|
||||
0.0,
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
path string
|
||||
o interface{}
|
||||
want interface{}
|
||||
wantErr bool
|
||||
}{
|
||||
{path: "s", o: st, want: "abc"},
|
||||
{path: "i", o: st, want: 13},
|
||||
{path: "f", o: st, want: true},
|
||||
{path: "nest.x", o: st, want: "xyz"},
|
||||
{path: "nest_ptr.x", o: st, want: "yyy"},
|
||||
// Error cases.
|
||||
{path: "", o: st, wantErr: true},
|
||||
{path: "no_such_field", o: st, wantErr: true},
|
||||
{path: "s.invalid_type", o: st, wantErr: true},
|
||||
{path: "unhandled", o: st, wantErr: true},
|
||||
{path: "nest.x", o: &struct{ Nest *nest }{}, wantErr: true},
|
||||
} {
|
||||
o, err := extractValue(tc.path, tc.o)
|
||||
gotErr := err != nil
|
||||
if gotErr != tc.wantErr {
|
||||
t.Errorf("extractValue(%v, %+v) = %v, %v; gotErr = %v, tc.wantErr = %v", tc.path, tc.o, o, err, gotErr, tc.wantErr)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(o, tc.want) {
|
||||
t.Errorf("extractValue(%v, %+v) = %v, nil; want %v, nil", tc.path, tc.o, o, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
99
pkg/cloudprovider/providers/gce/cloud/gce_projects.go
Normal file
99
pkg/cloudprovider/providers/gce/cloud/gce_projects.go
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
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 cloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
"google.golang.org/api/googleapi"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/meta"
|
||||
)
|
||||
|
||||
// ProjectsOps is the manually implemented methods for the Projects service.
|
||||
type ProjectsOps interface {
|
||||
Get(ctx context.Context, projectID string) (*compute.Project, error)
|
||||
SetCommonInstanceMetadata(ctx context.Context, projectID string, m *compute.Metadata) error
|
||||
}
|
||||
|
||||
// MockProjectOpsState is stored in the mock.X field.
|
||||
type MockProjectOpsState struct {
|
||||
metadata map[string]*compute.Metadata
|
||||
}
|
||||
|
||||
// Get a project by projectID.
|
||||
func (m *MockProjects) Get(ctx context.Context, projectID string) (*compute.Project, error) {
|
||||
m.Lock.Lock()
|
||||
defer m.Lock.Unlock()
|
||||
|
||||
if p, ok := m.Objects[*meta.GlobalKey(projectID)]; ok {
|
||||
return p.ToGA(), nil
|
||||
}
|
||||
return nil, &googleapi.Error{
|
||||
Code: http.StatusNotFound,
|
||||
Message: fmt.Sprintf("MockProjects %v not found", projectID),
|
||||
}
|
||||
}
|
||||
|
||||
// Get a project by projectID.
|
||||
func (g *GCEProjects) Get(ctx context.Context, projectID string) (*compute.Project, error) {
|
||||
rk := &RateLimitKey{
|
||||
ProjectID: projectID,
|
||||
Operation: "Get",
|
||||
Version: meta.Version("ga"),
|
||||
Service: "Projects",
|
||||
}
|
||||
if err := g.s.RateLimiter.Accept(ctx, rk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
call := g.s.GA.Projects.Get(projectID)
|
||||
call.Context(ctx)
|
||||
return call.Do()
|
||||
}
|
||||
|
||||
// SetCommonInstanceMetadata for a given project.
|
||||
func (m *MockProjects) SetCommonInstanceMetadata(ctx context.Context, projectID string, meta *compute.Metadata) error {
|
||||
if m.X == nil {
|
||||
m.X = &MockProjectOpsState{metadata: map[string]*compute.Metadata{}}
|
||||
}
|
||||
state := m.X.(*MockProjectOpsState)
|
||||
state.metadata[projectID] = meta
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetCommonInstanceMetadata for a given project.
|
||||
func (g *GCEProjects) SetCommonInstanceMetadata(ctx context.Context, projectID string, m *compute.Metadata) error {
|
||||
rk := &RateLimitKey{
|
||||
ProjectID: projectID,
|
||||
Operation: "SetCommonInstanceMetadata",
|
||||
Version: meta.Version("ga"),
|
||||
Service: "Projects",
|
||||
}
|
||||
if err := g.s.RateLimiter.Accept(ctx, rk); err != nil {
|
||||
return err
|
||||
}
|
||||
call := g.s.GA.Projects.SetCommonInstanceMetadata(projectID, m)
|
||||
call.Context(ctx)
|
||||
|
||||
op, err := call.Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return g.s.WaitForCompletion(ctx, op)
|
||||
}
|
10446
pkg/cloudprovider/providers/gce/cloud/gen.go
Normal file
10446
pkg/cloudprovider/providers/gce/cloud/gen.go
Normal file
File diff suppressed because it is too large
Load Diff
30
pkg/cloudprovider/providers/gce/cloud/gen/BUILD
Normal file
30
pkg/cloudprovider/providers/gce/cloud/gen/BUILD
Normal file
@ -0,0 +1,30 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["main.go"],
|
||||
importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/gen",
|
||||
visibility = ["//visibility:private"],
|
||||
deps = ["//pkg/cloudprovider/providers/gce/cloud/meta:go_default_library"],
|
||||
)
|
||||
|
||||
go_binary(
|
||||
name = "gen",
|
||||
embed = [":go_default_library"],
|
||||
importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/gen",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
1150
pkg/cloudprovider/providers/gce/cloud/gen/main.go
Normal file
1150
pkg/cloudprovider/providers/gce/cloud/gen/main.go
Normal file
File diff suppressed because it is too large
Load Diff
1749
pkg/cloudprovider/providers/gce/cloud/gen_test.go
Normal file
1749
pkg/cloudprovider/providers/gce/cloud/gen_test.go
Normal file
File diff suppressed because it is too large
Load Diff
41
pkg/cloudprovider/providers/gce/cloud/meta/BUILD
Normal file
41
pkg/cloudprovider/providers/gce/cloud/meta/BUILD
Normal file
@ -0,0 +1,41 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"doc.go",
|
||||
"key.go",
|
||||
"meta.go",
|
||||
"method.go",
|
||||
"service.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/meta",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/google.golang.org/api/compute/v0.alpha:go_default_library",
|
||||
"//vendor/google.golang.org/api/compute/v0.beta:go_default_library",
|
||||
"//vendor/google.golang.org/api/compute/v1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["key_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/meta",
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
19
pkg/cloudprovider/providers/gce/cloud/meta/doc.go
Normal file
19
pkg/cloudprovider/providers/gce/cloud/meta/doc.go
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
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 meta contains the meta description of the GCE cloud types to
|
||||
// generate code for.
|
||||
package meta
|
96
pkg/cloudprovider/providers/gce/cloud/meta/key.go
Normal file
96
pkg/cloudprovider/providers/gce/cloud/meta/key.go
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
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 meta
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Key for a GCP resource.
|
||||
type Key struct {
|
||||
Name string
|
||||
Zone string
|
||||
Region string
|
||||
}
|
||||
|
||||
// KeyType is the type of the key.
|
||||
type KeyType string
|
||||
|
||||
const (
|
||||
// Zonal key type.
|
||||
Zonal = "zonal"
|
||||
// Regional key type.
|
||||
Regional = "regional"
|
||||
// Global key type.
|
||||
Global = "global"
|
||||
)
|
||||
|
||||
// ZonalKey returns the key for a zonal resource.
|
||||
func ZonalKey(name, zone string) *Key {
|
||||
return &Key{name, zone, ""}
|
||||
}
|
||||
|
||||
// RegionalKey returns the key for a regional resource.
|
||||
func RegionalKey(name, region string) *Key {
|
||||
return &Key{name, "", region}
|
||||
}
|
||||
|
||||
// GlobalKey returns the key for a global resource.
|
||||
func GlobalKey(name string) *Key {
|
||||
return &Key{name, "", ""}
|
||||
}
|
||||
|
||||
// Type returns the type of the key.
|
||||
func (k *Key) Type() KeyType {
|
||||
switch {
|
||||
case k.Zone != "":
|
||||
return Zonal
|
||||
case k.Region != "":
|
||||
return Regional
|
||||
default:
|
||||
return Global
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of the key.
|
||||
func (k Key) String() string {
|
||||
switch k.Type() {
|
||||
case Zonal:
|
||||
return fmt.Sprintf("Key{%q, zone: %q}", k.Name, k.Zone)
|
||||
case Regional:
|
||||
return fmt.Sprintf("Key{%q, region: %q}", k.Name, k.Region)
|
||||
default:
|
||||
return fmt.Sprintf("Key{%q}", k.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Valid is true if the key is valid.
|
||||
func (k *Key) Valid(typeName string) bool {
|
||||
if k.Zone != "" && k.Region != "" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// KeysToMap creates a map[Key]bool from a list of keys.
|
||||
func KeysToMap(keys ...Key) map[Key]bool {
|
||||
ret := map[Key]bool{}
|
||||
for _, k := range keys {
|
||||
ret[k] = true
|
||||
}
|
||||
return ret
|
||||
}
|
75
pkg/cloudprovider/providers/gce/cloud/meta/key_test.go
Normal file
75
pkg/cloudprovider/providers/gce/cloud/meta/key_test.go
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
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 meta
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestKeyType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range []struct {
|
||||
key *Key
|
||||
want KeyType
|
||||
}{
|
||||
{GlobalKey("abc"), Global},
|
||||
{ZonalKey("abc", "us-central1-b"), Zonal},
|
||||
{RegionalKey("abc", "us-central1"), Regional},
|
||||
} {
|
||||
if tc.key.Type() != tc.want {
|
||||
t.Errorf("key.Type() == %v, want %v", tc.key.Type(), tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, k := range []*Key{
|
||||
GlobalKey("abc"),
|
||||
RegionalKey("abc", "us-central1"),
|
||||
ZonalKey("abc", "us-central1-b"),
|
||||
} {
|
||||
if k.String() == "" {
|
||||
t.Errorf(`k.String() = "", want non-empty`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyValid(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
region := "us-central1"
|
||||
zone := "us-central1-b"
|
||||
|
||||
for _, tc := range []struct {
|
||||
key *Key
|
||||
typeName string
|
||||
want bool
|
||||
}{
|
||||
// Note: these test cases need to be synchronized with the
|
||||
// actual settings for each type.
|
||||
{GlobalKey("abc"), "UrlMap", true},
|
||||
{&Key{"abc", zone, region}, "UrlMap", false},
|
||||
} {
|
||||
valid := tc.key.Valid(tc.typeName)
|
||||
if valid != tc.want {
|
||||
t.Errorf("key %+v, type %v; key.Valid() = %v, want %v", tc.key, tc.typeName, valid, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
372
pkg/cloudprovider/providers/gce/cloud/meta/meta.go
Normal file
372
pkg/cloudprovider/providers/gce/cloud/meta/meta.go
Normal file
@ -0,0 +1,372 @@
|
||||
/*
|
||||
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 meta
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
alpha "google.golang.org/api/compute/v0.alpha"
|
||||
beta "google.golang.org/api/compute/v0.beta"
|
||||
ga "google.golang.org/api/compute/v1"
|
||||
)
|
||||
|
||||
// Version of the API (ga, alpha, beta).
|
||||
type Version string
|
||||
|
||||
const (
|
||||
// NoGet prevents the Get() method from being generated.
|
||||
NoGet = 1 << iota
|
||||
// NoList prevents the List() method from being generated.
|
||||
NoList = 1 << iota
|
||||
// NoDelete prevents the Delete() method from being generated.
|
||||
NoDelete = 1 << iota
|
||||
// NoInsert prevents the Insert() method from being generated.
|
||||
NoInsert = 1 << iota
|
||||
// CustomOps specifies that an empty interface xxxOps will be generated to
|
||||
// enable custom method calls to be attached to the generated service
|
||||
// interface.
|
||||
CustomOps = 1 << iota
|
||||
// AggregatedList will generated a method for AggregatedList().
|
||||
AggregatedList = 1 << iota
|
||||
|
||||
// ReadOnly specifies that the given resource is read-only and should not
|
||||
// have insert() or delete() methods generated for the wrapper.
|
||||
ReadOnly = NoDelete | NoInsert
|
||||
|
||||
// VersionGA is the API version in compute.v1.
|
||||
VersionGA Version = "ga"
|
||||
// VersionAlpha is the API version in computer.v0.alpha.
|
||||
VersionAlpha Version = "alpha"
|
||||
// VersionBeta is the API version in computer.v0.beta.
|
||||
VersionBeta Version = "beta"
|
||||
)
|
||||
|
||||
// AllVersions is a list of all versions of the GCE API.
|
||||
var AllVersions = []Version{
|
||||
VersionGA,
|
||||
VersionAlpha,
|
||||
VersionBeta,
|
||||
}
|
||||
|
||||
// AllServices are a list of all the services to generate code for. Keep
|
||||
// this list in lexiographical order by object type.
|
||||
var AllServices = []*ServiceInfo{
|
||||
{
|
||||
Object: "Address",
|
||||
Service: "Addresses",
|
||||
Resource: "addresses",
|
||||
keyType: Regional,
|
||||
serviceType: reflect.TypeOf(&ga.AddressesService{}),
|
||||
},
|
||||
{
|
||||
Object: "Address",
|
||||
Service: "Addresses",
|
||||
Resource: "addresses",
|
||||
version: VersionAlpha,
|
||||
keyType: Regional,
|
||||
serviceType: reflect.TypeOf(&alpha.AddressesService{}),
|
||||
},
|
||||
{
|
||||
Object: "Address",
|
||||
Service: "Addresses",
|
||||
Resource: "addresses",
|
||||
version: VersionBeta,
|
||||
keyType: Regional,
|
||||
serviceType: reflect.TypeOf(&beta.AddressesService{}),
|
||||
},
|
||||
{
|
||||
Object: "Address",
|
||||
Service: "GlobalAddresses",
|
||||
Resource: "addresses",
|
||||
keyType: Global,
|
||||
serviceType: reflect.TypeOf(&ga.GlobalAddressesService{}),
|
||||
},
|
||||
{
|
||||
Object: "BackendService",
|
||||
Service: "BackendServices",
|
||||
Resource: "backendServices",
|
||||
keyType: Global,
|
||||
serviceType: reflect.TypeOf(&ga.BackendServicesService{}),
|
||||
additionalMethods: []string{
|
||||
"GetHealth",
|
||||
"Update",
|
||||
},
|
||||
},
|
||||
{
|
||||
Object: "BackendService",
|
||||
Service: "BackendServices",
|
||||
Resource: "backendServices",
|
||||
version: VersionAlpha,
|
||||
keyType: Global,
|
||||
serviceType: reflect.TypeOf(&alpha.BackendServicesService{}),
|
||||
additionalMethods: []string{"Update"},
|
||||
},
|
||||
{
|
||||
Object: "BackendService",
|
||||
Service: "RegionBackendServices",
|
||||
Resource: "backendServices",
|
||||
version: VersionAlpha,
|
||||
keyType: Regional,
|
||||
serviceType: reflect.TypeOf(&alpha.RegionBackendServicesService{}),
|
||||
additionalMethods: []string{
|
||||
"GetHealth",
|
||||
"Update",
|
||||
},
|
||||
},
|
||||
{
|
||||
Object: "Disk",
|
||||
Service: "Disks",
|
||||
Resource: "disks",
|
||||
keyType: Zonal,
|
||||
serviceType: reflect.TypeOf(&ga.DisksService{}),
|
||||
},
|
||||
{
|
||||
Object: "Disk",
|
||||
Service: "Disks",
|
||||
Resource: "disks",
|
||||
version: VersionAlpha,
|
||||
keyType: Zonal,
|
||||
serviceType: reflect.TypeOf(&alpha.DisksService{}),
|
||||
},
|
||||
{
|
||||
Object: "Disk",
|
||||
Service: "RegionDisks",
|
||||
Resource: "disks",
|
||||
version: VersionAlpha,
|
||||
keyType: Regional,
|
||||
serviceType: reflect.TypeOf(&alpha.DisksService{}),
|
||||
},
|
||||
{
|
||||
Object: "Firewall",
|
||||
Service: "Firewalls",
|
||||
Resource: "firewalls",
|
||||
keyType: Global,
|
||||
serviceType: reflect.TypeOf(&ga.FirewallsService{}),
|
||||
additionalMethods: []string{
|
||||
"Update",
|
||||
},
|
||||
},
|
||||
{
|
||||
Object: "ForwardingRule",
|
||||
Service: "ForwardingRules",
|
||||
Resource: "forwardingRules",
|
||||
keyType: Regional,
|
||||
serviceType: reflect.TypeOf(&ga.ForwardingRulesService{}),
|
||||
},
|
||||
{
|
||||
Object: "ForwardingRule",
|
||||
Service: "ForwardingRules",
|
||||
Resource: "forwardingRules",
|
||||
version: VersionAlpha,
|
||||
keyType: Regional,
|
||||
serviceType: reflect.TypeOf(&alpha.ForwardingRulesService{}),
|
||||
},
|
||||
{
|
||||
Object: "ForwardingRule",
|
||||
Service: "GlobalForwardingRules",
|
||||
Resource: "forwardingRules",
|
||||
keyType: Global,
|
||||
serviceType: reflect.TypeOf(&ga.GlobalForwardingRulesService{}),
|
||||
additionalMethods: []string{
|
||||
"SetTarget",
|
||||
},
|
||||
},
|
||||
{
|
||||
Object: "HealthCheck",
|
||||
Service: "HealthChecks",
|
||||
Resource: "healthChecks",
|
||||
keyType: Global,
|
||||
serviceType: reflect.TypeOf(&ga.HealthChecksService{}),
|
||||
additionalMethods: []string{
|
||||
"Update",
|
||||
},
|
||||
},
|
||||
{
|
||||
Object: "HealthCheck",
|
||||
Service: "HealthChecks",
|
||||
Resource: "healthChecks",
|
||||
version: VersionAlpha,
|
||||
keyType: Global,
|
||||
serviceType: reflect.TypeOf(&alpha.HealthChecksService{}),
|
||||
additionalMethods: []string{
|
||||
"Update",
|
||||
},
|
||||
},
|
||||
{
|
||||
Object: "HttpHealthCheck",
|
||||
Service: "HttpHealthChecks",
|
||||
Resource: "httpHealthChecks",
|
||||
keyType: Global,
|
||||
serviceType: reflect.TypeOf(&ga.HttpHealthChecksService{}),
|
||||
additionalMethods: []string{
|
||||
"Update",
|
||||
},
|
||||
},
|
||||
{
|
||||
Object: "HttpsHealthCheck",
|
||||
Service: "HttpsHealthChecks",
|
||||
Resource: "httpsHealthChecks",
|
||||
keyType: Global,
|
||||
serviceType: reflect.TypeOf(&ga.HttpsHealthChecksService{}),
|
||||
additionalMethods: []string{
|
||||
"Update",
|
||||
},
|
||||
},
|
||||
{
|
||||
Object: "InstanceGroup",
|
||||
Service: "InstanceGroups",
|
||||
Resource: "instanceGroups",
|
||||
keyType: Zonal,
|
||||
serviceType: reflect.TypeOf(&ga.InstanceGroupsService{}),
|
||||
additionalMethods: []string{
|
||||
"AddInstances",
|
||||
"ListInstances",
|
||||
"RemoveInstances",
|
||||
"SetNamedPorts",
|
||||
},
|
||||
},
|
||||
{
|
||||
Object: "Instance",
|
||||
Service: "Instances",
|
||||
Resource: "instances",
|
||||
keyType: Zonal,
|
||||
serviceType: reflect.TypeOf(&ga.InstancesService{}),
|
||||
additionalMethods: []string{
|
||||
"AttachDisk",
|
||||
"DetachDisk",
|
||||
},
|
||||
},
|
||||
{
|
||||
Object: "Instance",
|
||||
Service: "Instances",
|
||||
Resource: "instances",
|
||||
version: VersionBeta,
|
||||
keyType: Zonal,
|
||||
serviceType: reflect.TypeOf(&beta.InstancesService{}),
|
||||
additionalMethods: []string{
|
||||
"AttachDisk",
|
||||
"DetachDisk",
|
||||
},
|
||||
},
|
||||
{
|
||||
Object: "Instance",
|
||||
Service: "Instances",
|
||||
Resource: "instances",
|
||||
version: VersionAlpha,
|
||||
keyType: Zonal,
|
||||
serviceType: reflect.TypeOf(&alpha.InstancesService{}),
|
||||
additionalMethods: []string{
|
||||
"AttachDisk",
|
||||
"DetachDisk",
|
||||
"UpdateNetworkInterface",
|
||||
},
|
||||
},
|
||||
{
|
||||
Object: "NetworkEndpointGroup",
|
||||
Service: "NetworkEndpointGroups",
|
||||
Resource: "networkEndpointGroups",
|
||||
version: VersionAlpha,
|
||||
keyType: Zonal,
|
||||
serviceType: reflect.TypeOf(&alpha.NetworkEndpointGroupsService{}),
|
||||
additionalMethods: []string{
|
||||
"AttachNetworkEndpoints",
|
||||
"DetachNetworkEndpoints",
|
||||
},
|
||||
options: AggregatedList,
|
||||
},
|
||||
{
|
||||
Object: "Project",
|
||||
Service: "Projects",
|
||||
Resource: "projects",
|
||||
keyType: Global,
|
||||
// Generate only the stub with no methods.
|
||||
options: NoGet | NoList | NoInsert | NoDelete | CustomOps,
|
||||
serviceType: reflect.TypeOf(&ga.ProjectsService{}),
|
||||
},
|
||||
{
|
||||
Object: "Region",
|
||||
Service: "Regions",
|
||||
Resource: "regions",
|
||||
keyType: Global,
|
||||
options: ReadOnly,
|
||||
serviceType: reflect.TypeOf(&ga.RegionsService{}),
|
||||
},
|
||||
{
|
||||
Object: "Route",
|
||||
Service: "Routes",
|
||||
Resource: "routes",
|
||||
keyType: Global,
|
||||
serviceType: reflect.TypeOf(&ga.RoutesService{}),
|
||||
},
|
||||
{
|
||||
Object: "SslCertificate",
|
||||
Service: "SslCertificates",
|
||||
Resource: "sslCertificates",
|
||||
keyType: Global,
|
||||
serviceType: reflect.TypeOf(&ga.SslCertificatesService{}),
|
||||
},
|
||||
{
|
||||
Object: "TargetHttpProxy",
|
||||
Service: "TargetHttpProxies",
|
||||
Resource: "targetHttpProxies",
|
||||
keyType: Global,
|
||||
serviceType: reflect.TypeOf(&ga.TargetHttpProxiesService{}),
|
||||
additionalMethods: []string{
|
||||
"SetUrlMap",
|
||||
},
|
||||
},
|
||||
{
|
||||
Object: "TargetHttpsProxy",
|
||||
Service: "TargetHttpsProxies",
|
||||
Resource: "targetHttpsProxies",
|
||||
keyType: Global,
|
||||
serviceType: reflect.TypeOf(&ga.TargetHttpsProxiesService{}),
|
||||
additionalMethods: []string{
|
||||
"SetSslCertificates",
|
||||
"SetUrlMap",
|
||||
},
|
||||
},
|
||||
{
|
||||
Object: "TargetPool",
|
||||
Service: "TargetPools",
|
||||
Resource: "targetPools",
|
||||
keyType: Regional,
|
||||
serviceType: reflect.TypeOf(&ga.TargetPoolsService{}),
|
||||
additionalMethods: []string{
|
||||
"AddInstance",
|
||||
"RemoveInstance",
|
||||
},
|
||||
},
|
||||
{
|
||||
Object: "UrlMap",
|
||||
Service: "UrlMaps",
|
||||
Resource: "urlMaps",
|
||||
keyType: Global,
|
||||
serviceType: reflect.TypeOf(&ga.UrlMapsService{}),
|
||||
additionalMethods: []string{
|
||||
"Update",
|
||||
},
|
||||
},
|
||||
{
|
||||
Object: "Zone",
|
||||
Service: "Zones",
|
||||
Resource: "zones",
|
||||
keyType: Global,
|
||||
options: ReadOnly,
|
||||
serviceType: reflect.TypeOf(&ga.ZonesService{}),
|
||||
},
|
||||
}
|
250
pkg/cloudprovider/providers/gce/cloud/meta/method.go
Normal file
250
pkg/cloudprovider/providers/gce/cloud/meta/method.go
Normal file
@ -0,0 +1,250 @@
|
||||
/*
|
||||
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 meta
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
func newArg(t reflect.Type) *arg {
|
||||
ret := &arg{}
|
||||
|
||||
// Dereference the pointer types to get at the underlying concrete type.
|
||||
Loop:
|
||||
for {
|
||||
switch t.Kind() {
|
||||
case reflect.Ptr:
|
||||
ret.numPtr++
|
||||
t = t.Elem()
|
||||
default:
|
||||
ret.pkg = t.PkgPath()
|
||||
ret.typeName += t.Name()
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
type arg struct {
|
||||
pkg, typeName string
|
||||
numPtr int
|
||||
}
|
||||
|
||||
func (a *arg) normalizedPkg() string {
|
||||
if a.pkg == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Strip the repo.../vendor/ prefix from the package path if present.
|
||||
parts := strings.Split(a.pkg, "/")
|
||||
// Remove vendor prefix.
|
||||
for i := 0; i < len(parts); i++ {
|
||||
if parts[i] == "vendor" {
|
||||
parts = parts[i+1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
switch strings.Join(parts, "/") {
|
||||
case "google.golang.org/api/compute/v1":
|
||||
return "ga."
|
||||
case "google.golang.org/api/compute/v0.alpha":
|
||||
return "alpha."
|
||||
case "google.golang.org/api/compute/v0.beta":
|
||||
return "beta."
|
||||
default:
|
||||
panic(fmt.Errorf("unhandled package %q", a.pkg))
|
||||
}
|
||||
}
|
||||
|
||||
func (a *arg) String() string {
|
||||
var ret string
|
||||
for i := 0; i < a.numPtr; i++ {
|
||||
ret += "*"
|
||||
}
|
||||
ret += a.normalizedPkg()
|
||||
ret += a.typeName
|
||||
return ret
|
||||
}
|
||||
|
||||
// newMethod returns a newly initialized method.
|
||||
func newMethod(s *ServiceInfo, m reflect.Method) *Method {
|
||||
ret := &Method{s, m, ""}
|
||||
ret.init()
|
||||
return ret
|
||||
}
|
||||
|
||||
// Method is used to generate the calling code for non-standard methods.
|
||||
type Method struct {
|
||||
*ServiceInfo
|
||||
m reflect.Method
|
||||
|
||||
ReturnType string
|
||||
}
|
||||
|
||||
// argsSkip is the number of arguments to skip when generating the
|
||||
// synthesized method.
|
||||
func (mr *Method) argsSkip() int {
|
||||
switch mr.keyType {
|
||||
case Zonal:
|
||||
return 4
|
||||
case Regional:
|
||||
return 4
|
||||
case Global:
|
||||
return 3
|
||||
}
|
||||
panic(fmt.Errorf("invalid KeyType %v", mr.keyType))
|
||||
}
|
||||
|
||||
// args return a list of arguments to the method, skipping the first skip
|
||||
// elements. If nameArgs is true, then the arguments will include a generated
|
||||
// parameter name (arg<N>). prefix will be added to the parameters.
|
||||
func (mr *Method) args(skip int, nameArgs bool, prefix []string) []string {
|
||||
var args []*arg
|
||||
fType := mr.m.Func.Type()
|
||||
for i := 0; i < fType.NumIn(); i++ {
|
||||
t := fType.In(i)
|
||||
args = append(args, newArg(t))
|
||||
}
|
||||
|
||||
var a []string
|
||||
for i := skip; i < fType.NumIn(); i++ {
|
||||
if nameArgs {
|
||||
a = append(a, fmt.Sprintf("arg%d %s", i-skip, args[i]))
|
||||
} else {
|
||||
a = append(a, args[i].String())
|
||||
}
|
||||
}
|
||||
return append(prefix, a...)
|
||||
}
|
||||
|
||||
// init the method, preforming some rudimentary static checking.
|
||||
func (mr *Method) init() {
|
||||
fType := mr.m.Func.Type()
|
||||
if fType.NumIn() < mr.argsSkip() {
|
||||
err := fmt.Errorf("method %q.%q, arity = %d which is less than required (< %d)",
|
||||
mr.Service, mr.Name(), fType.NumIn(), mr.argsSkip())
|
||||
panic(err)
|
||||
}
|
||||
// Skipped args should all be string (they will be projectID, zone, region etc).
|
||||
for i := 1; i < mr.argsSkip(); i++ {
|
||||
if fType.In(i).Kind() != reflect.String {
|
||||
panic(fmt.Errorf("method %q.%q: skipped args can only be strings", mr.Service, mr.Name()))
|
||||
}
|
||||
}
|
||||
// Return of the method must return a single value of type *xxxCall.
|
||||
if fType.NumOut() != 1 || fType.Out(0).Kind() != reflect.Ptr || !strings.HasSuffix(fType.Out(0).Elem().Name(), "Call") {
|
||||
panic(fmt.Errorf("method %q.%q: generator only supports methods returning an *xxxCall object",
|
||||
mr.Service, mr.Name()))
|
||||
}
|
||||
returnType := fType.Out(0)
|
||||
returnTypeName := fType.Out(0).Elem().Name()
|
||||
// xxxCall must have a Do() method.
|
||||
doMethod, ok := returnType.MethodByName("Do")
|
||||
if !ok {
|
||||
panic(fmt.Errorf("method %q.%q: return type %q does not have a Do() method",
|
||||
mr.Service, mr.Name(), returnTypeName))
|
||||
}
|
||||
// Do() method must return (*T, error).
|
||||
switch doMethod.Func.Type().NumOut() {
|
||||
case 2:
|
||||
glog.Infof("Method %q.%q: return type %q of Do() = %v, %v",
|
||||
mr.Service, mr.Name(), returnTypeName, doMethod.Func.Type().Out(0), doMethod.Func.Type().Out(1))
|
||||
out0 := doMethod.Func.Type().Out(0)
|
||||
if out0.Kind() != reflect.Ptr {
|
||||
panic(fmt.Errorf("method %q.%q: return type %q of Do() = S, _; S must be pointer type (%v)",
|
||||
mr.Service, mr.Name(), returnTypeName, out0))
|
||||
}
|
||||
mr.ReturnType = out0.Elem().Name()
|
||||
if out0.Elem().Name() == "Operation" {
|
||||
glog.Infof("Method %q.%q is an *Operation", mr.Service, mr.Name())
|
||||
} else {
|
||||
glog.Infof("Method %q.%q returns %v", mr.Service, mr.Name(), out0)
|
||||
}
|
||||
// Second argument must be "error".
|
||||
if doMethod.Func.Type().Out(1).Name() != "error" {
|
||||
panic(fmt.Errorf("method %q.%q: return type %q of Do() = S, T; T must be 'error'",
|
||||
mr.Service, mr.Name(), returnTypeName))
|
||||
}
|
||||
break
|
||||
default:
|
||||
panic(fmt.Errorf("method %q.%q: %q Do() return type is not handled by the generator",
|
||||
mr.Service, mr.Name(), returnTypeName))
|
||||
}
|
||||
}
|
||||
|
||||
// Name is the name of the method.
|
||||
func (mr *Method) Name() string {
|
||||
return mr.m.Name
|
||||
}
|
||||
|
||||
// CallArgs is a list of comma separated "argN" used for calling the method.
|
||||
// For example, if the method has two additional arguments, this will return
|
||||
// "arg0, arg1".
|
||||
func (mr *Method) CallArgs() string {
|
||||
var args []string
|
||||
for i := mr.argsSkip(); i < mr.m.Func.Type().NumIn(); i++ {
|
||||
args = append(args, fmt.Sprintf("arg%d", i-mr.argsSkip()))
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(", %s", strings.Join(args, ", "))
|
||||
}
|
||||
|
||||
// MockHookName is the name of the hook function in the mock.
|
||||
func (mr *Method) MockHookName() string {
|
||||
return mr.m.Name + "Hook"
|
||||
}
|
||||
|
||||
// MockHook is the definition of the hook function.
|
||||
func (mr *Method) MockHook() string {
|
||||
args := mr.args(mr.argsSkip(), false, []string{
|
||||
fmt.Sprintf("*%s", mr.MockWrapType()),
|
||||
"context.Context",
|
||||
"meta.Key",
|
||||
})
|
||||
if mr.ReturnType == "Operation" {
|
||||
return fmt.Sprintf("%v func(%v) error", mr.MockHookName(), strings.Join(args, ", "))
|
||||
}
|
||||
return fmt.Sprintf("%v func(%v) (*%v.%v, error)", mr.MockHookName(), strings.Join(args, ", "), mr.Version(), mr.ReturnType)
|
||||
}
|
||||
|
||||
// FcnArgs is the function signature for the definition of the method.
|
||||
func (mr *Method) FcnArgs() string {
|
||||
args := mr.args(mr.argsSkip(), true, []string{
|
||||
"ctx context.Context",
|
||||
"key meta.Key",
|
||||
})
|
||||
|
||||
if mr.ReturnType == "Operation" {
|
||||
return fmt.Sprintf("%v(%v) error", mr.m.Name, strings.Join(args, ", "))
|
||||
}
|
||||
return fmt.Sprintf("%v(%v) (*%v.%v, error)", mr.m.Name, strings.Join(args, ", "), mr.Version(), mr.ReturnType)
|
||||
}
|
||||
|
||||
// InterfaceFunc is the function declaration of the method in the interface.
|
||||
func (mr *Method) InterfaceFunc() string {
|
||||
args := mr.args(mr.argsSkip(), false, []string{"context.Context", "meta.Key"})
|
||||
if mr.ReturnType == "Operation" {
|
||||
return fmt.Sprintf("%v(%v) error", mr.m.Name, strings.Join(args, ", "))
|
||||
}
|
||||
return fmt.Sprintf("%v(%v) (*%v.%v, error)", mr.m.Name, strings.Join(args, ", "), mr.Version(), mr.ReturnType)
|
||||
}
|
277
pkg/cloudprovider/providers/gce/cloud/meta/service.go
Normal file
277
pkg/cloudprovider/providers/gce/cloud/meta/service.go
Normal file
@ -0,0 +1,277 @@
|
||||
/*
|
||||
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 meta
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// ServiceInfo defines the entry for a Service that code will be generated for.
|
||||
type ServiceInfo struct {
|
||||
// Object is the Go name of the object type that the service deals
|
||||
// with. Example: "ForwardingRule".
|
||||
Object string
|
||||
// Service is the Go name of the service struct i.e. where the methods
|
||||
// are defined. Examples: "GlobalForwardingRules".
|
||||
Service string
|
||||
// Resource is the plural noun of the resource in the compute API URL (e.g.
|
||||
// "forwardingRules").
|
||||
Resource string
|
||||
// version if unspecified will be assumed to be VersionGA.
|
||||
version Version
|
||||
keyType KeyType
|
||||
serviceType reflect.Type
|
||||
|
||||
additionalMethods []string
|
||||
options int
|
||||
aggregatedListField string
|
||||
}
|
||||
|
||||
// Version returns the version of the Service, defaulting to GA if APIVersion
|
||||
// is empty.
|
||||
func (i *ServiceInfo) Version() Version {
|
||||
if i.version == "" {
|
||||
return VersionGA
|
||||
}
|
||||
return i.version
|
||||
}
|
||||
|
||||
// VersionTitle returns the capitalized golang CamelCase name for the version.
|
||||
func (i *ServiceInfo) VersionTitle() string {
|
||||
switch i.Version() {
|
||||
case VersionGA:
|
||||
return "GA"
|
||||
case VersionAlpha:
|
||||
return "Alpha"
|
||||
case VersionBeta:
|
||||
return "Beta"
|
||||
}
|
||||
panic(fmt.Errorf("invalid version %q", i.Version()))
|
||||
}
|
||||
|
||||
// WrapType is the name of the wrapper service type.
|
||||
func (i *ServiceInfo) WrapType() string {
|
||||
switch i.Version() {
|
||||
case VersionGA:
|
||||
return i.Service
|
||||
case VersionAlpha:
|
||||
return "Alpha" + i.Service
|
||||
case VersionBeta:
|
||||
return "Beta" + i.Service
|
||||
}
|
||||
return "Invalid"
|
||||
}
|
||||
|
||||
// WrapTypeOps is the name of the additional operations type.
|
||||
func (i *ServiceInfo) WrapTypeOps() string {
|
||||
return i.WrapType() + "Ops"
|
||||
}
|
||||
|
||||
// FQObjectType is fully qualified name of the object (e.g. compute.Instance).
|
||||
func (i *ServiceInfo) FQObjectType() string {
|
||||
return fmt.Sprintf("%v.%v", i.Version(), i.Object)
|
||||
}
|
||||
|
||||
// ObjectListType is the compute List type for the object (contains Items field).
|
||||
func (i *ServiceInfo) ObjectListType() string {
|
||||
return fmt.Sprintf("%v.%vList", i.Version(), i.Object)
|
||||
}
|
||||
|
||||
// ObjectAggregatedListType is the compute List type for the object (contains Items field).
|
||||
func (i *ServiceInfo) ObjectAggregatedListType() string {
|
||||
return fmt.Sprintf("%v.%vAggregatedList", i.Version(), i.Object)
|
||||
}
|
||||
|
||||
// MockWrapType is the name of the concrete mock for this type.
|
||||
func (i *ServiceInfo) MockWrapType() string {
|
||||
return "Mock" + i.WrapType()
|
||||
}
|
||||
|
||||
// MockField is the name of the field in the mock struct.
|
||||
func (i *ServiceInfo) MockField() string {
|
||||
return "Mock" + i.WrapType()
|
||||
}
|
||||
|
||||
// GCEWrapType is the name of the GCE wrapper type.
|
||||
func (i *ServiceInfo) GCEWrapType() string {
|
||||
return "GCE" + i.WrapType()
|
||||
}
|
||||
|
||||
// Field is the name of the GCE struct.
|
||||
func (i *ServiceInfo) Field() string {
|
||||
return "gce" + i.WrapType()
|
||||
}
|
||||
|
||||
// Methods returns a list of additional methods to generate code for.
|
||||
func (i *ServiceInfo) Methods() []*Method {
|
||||
methods := map[string]bool{}
|
||||
for _, m := range i.additionalMethods {
|
||||
methods[m] = true
|
||||
}
|
||||
|
||||
var ret []*Method
|
||||
for j := 0; j < i.serviceType.NumMethod(); j++ {
|
||||
m := i.serviceType.Method(j)
|
||||
if _, ok := methods[m.Name]; !ok {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, newMethod(i, m))
|
||||
methods[m.Name] = false
|
||||
}
|
||||
|
||||
for k, b := range methods {
|
||||
if b {
|
||||
panic(fmt.Errorf("method %q was not found in service %q", k, i.Service))
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// KeyIsGlobal is true if the key is global.
|
||||
func (i *ServiceInfo) KeyIsGlobal() bool {
|
||||
return i.keyType == Global
|
||||
}
|
||||
|
||||
// KeyIsRegional is true if the key is regional.
|
||||
func (i *ServiceInfo) KeyIsRegional() bool {
|
||||
return i.keyType == Regional
|
||||
}
|
||||
|
||||
// KeyIsZonal is true if the key is zonal.
|
||||
func (i *ServiceInfo) KeyIsZonal() bool {
|
||||
return i.keyType == Zonal
|
||||
}
|
||||
|
||||
// MakeKey returns the call used to create the appropriate key type.
|
||||
func (i *ServiceInfo) MakeKey(name, location string) string {
|
||||
switch i.keyType {
|
||||
case Global:
|
||||
return fmt.Sprintf("GlobalKey(%q)", name)
|
||||
case Regional:
|
||||
return fmt.Sprintf("RegionalKey(%q, %q)", name, location)
|
||||
case Zonal:
|
||||
return fmt.Sprintf("ZonalKey(%q, %q)", name, location)
|
||||
}
|
||||
return "Invalid"
|
||||
}
|
||||
|
||||
// GenerateGet is true if the method is to be generated.
|
||||
func (i *ServiceInfo) GenerateGet() bool {
|
||||
return i.options&NoGet == 0
|
||||
}
|
||||
|
||||
// GenerateList is true if the method is to be generated.
|
||||
func (i *ServiceInfo) GenerateList() bool {
|
||||
return i.options&NoList == 0
|
||||
}
|
||||
|
||||
// GenerateDelete is true if the method is to be generated.
|
||||
func (i *ServiceInfo) GenerateDelete() bool {
|
||||
return i.options&NoDelete == 0
|
||||
}
|
||||
|
||||
// GenerateInsert is true if the method is to be generated.
|
||||
func (i *ServiceInfo) GenerateInsert() bool {
|
||||
return i.options&NoInsert == 0
|
||||
}
|
||||
|
||||
// GenerateCustomOps is true if we should generated a xxxOps interface for
|
||||
// adding additional methods to the generated interface.
|
||||
func (i *ServiceInfo) GenerateCustomOps() bool {
|
||||
return i.options&CustomOps != 0
|
||||
}
|
||||
|
||||
// AggregatedList is true if the method is to be generated.
|
||||
func (i *ServiceInfo) AggregatedList() bool {
|
||||
return i.options&AggregatedList != 0
|
||||
}
|
||||
|
||||
// AggregatedListField is the name of the field used for the aggregated list
|
||||
// call. This is typically the same as the name of the service, but can be
|
||||
// customized by setting the aggregatedListField field.
|
||||
func (i *ServiceInfo) AggregatedListField() string {
|
||||
if i.aggregatedListField == "" {
|
||||
return i.Service
|
||||
}
|
||||
return i.aggregatedListField
|
||||
}
|
||||
|
||||
// ServiceGroup is a grouping of the same service but at different API versions.
|
||||
type ServiceGroup struct {
|
||||
Alpha *ServiceInfo
|
||||
Beta *ServiceInfo
|
||||
GA *ServiceInfo
|
||||
}
|
||||
|
||||
// Service returns any ServiceInfo object belonging to the ServiceGroup.
|
||||
func (sg *ServiceGroup) Service() string {
|
||||
switch {
|
||||
case sg.GA != nil:
|
||||
return sg.GA.Service
|
||||
case sg.Alpha != nil:
|
||||
return sg.Alpha.Service
|
||||
case sg.Beta != nil:
|
||||
return sg.Beta.Service
|
||||
default:
|
||||
panic(errors.New("service group is empty"))
|
||||
}
|
||||
}
|
||||
|
||||
// HasGA returns true if this object has a GA representation.
|
||||
func (sg *ServiceGroup) HasGA() bool {
|
||||
return sg.GA != nil
|
||||
}
|
||||
|
||||
// HasAlpha returns true if this object has a Alpha representation.
|
||||
func (sg *ServiceGroup) HasAlpha() bool {
|
||||
return sg.Alpha != nil
|
||||
}
|
||||
|
||||
// HasBeta returns true if this object has a Beta representation.
|
||||
func (sg *ServiceGroup) HasBeta() bool {
|
||||
return sg.Beta != nil
|
||||
}
|
||||
|
||||
// groupServices together by version.
|
||||
func groupServices(services []*ServiceInfo) map[string]*ServiceGroup {
|
||||
ret := map[string]*ServiceGroup{}
|
||||
for _, si := range services {
|
||||
if _, ok := ret[si.Service]; !ok {
|
||||
ret[si.Service] = &ServiceGroup{}
|
||||
}
|
||||
group := ret[si.Service]
|
||||
switch si.Version() {
|
||||
case VersionAlpha:
|
||||
group.Alpha = si
|
||||
case VersionBeta:
|
||||
group.Beta = si
|
||||
case VersionGA:
|
||||
group.GA = si
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// AllServicesByGroup is a map of service name to ServicesGroup.
|
||||
var AllServicesByGroup map[string]*ServiceGroup
|
||||
|
||||
func init() {
|
||||
AllServicesByGroup = groupServices(AllServices)
|
||||
}
|
150
pkg/cloudprovider/providers/gce/cloud/mock_test.go
Normal file
150
pkg/cloudprovider/providers/gce/cloud/mock_test.go
Normal 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 cloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
alpha "google.golang.org/api/compute/v0.alpha"
|
||||
beta "google.golang.org/api/compute/v0.beta"
|
||||
ga "google.golang.org/api/compute/v1"
|
||||
|
||||
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/filter"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/meta"
|
||||
)
|
||||
|
||||
func TestMocks(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// This test uses Addresses, but the logic that is generated is the same for
|
||||
// other basic objects.
|
||||
const region = "us-central1"
|
||||
|
||||
ctx := context.Background()
|
||||
mock := NewMockGCE()
|
||||
|
||||
keyAlpha := meta.RegionalKey("key-alpha", region)
|
||||
keyBeta := meta.RegionalKey("key-beta", region)
|
||||
keyGA := meta.RegionalKey("key-ga", region)
|
||||
key := keyAlpha
|
||||
|
||||
// Get not found.
|
||||
if _, err := mock.AlphaAddresses().Get(ctx, *key); err == nil {
|
||||
t.Errorf("AlphaAddresses().Get(%v, %v) = _, nil; want error", ctx, key)
|
||||
}
|
||||
if _, err := mock.BetaAddresses().Get(ctx, *key); err == nil {
|
||||
t.Errorf("BetaAddresses().Get(%v, %v) = _, nil; want error", ctx, key)
|
||||
}
|
||||
if _, err := mock.Addresses().Get(ctx, *key); err == nil {
|
||||
t.Errorf("Addresses().Get(%v, %v) = _, nil; want error", ctx, key)
|
||||
}
|
||||
// Insert.
|
||||
{
|
||||
obj := &alpha.Address{}
|
||||
if err := mock.AlphaAddresses().Insert(ctx, *keyAlpha, obj); err != nil {
|
||||
t.Errorf("AlphaAddresses().Insert(%v, %v, %v) = %v; want nil", ctx, key, obj, err)
|
||||
}
|
||||
}
|
||||
{
|
||||
obj := &beta.Address{}
|
||||
if err := mock.BetaAddresses().Insert(ctx, *keyBeta, obj); err != nil {
|
||||
t.Errorf("BetaAddresses().Insert(%v, %v, %v) = %v; want nil", ctx, key, obj, err)
|
||||
}
|
||||
}
|
||||
{
|
||||
obj := &ga.Address{}
|
||||
if err := mock.Addresses().Insert(ctx, *keyGA, &ga.Address{Name: "ga"}); err != nil {
|
||||
t.Errorf("Addresses().Insert(%v, %v, %v) = %v; want nil", ctx, key, obj, err)
|
||||
}
|
||||
}
|
||||
// Get across versions.
|
||||
if obj, err := mock.AlphaAddresses().Get(ctx, *key); err != nil {
|
||||
t.Errorf("AlphaAddresses().Get(%v, %v) = %v, %v; want nil", ctx, key, obj, err)
|
||||
}
|
||||
if obj, err := mock.BetaAddresses().Get(ctx, *key); err != nil {
|
||||
t.Errorf("BetaAddresses().Get(%v, %v) = %v, %v; want nil", ctx, key, obj, err)
|
||||
}
|
||||
if obj, err := mock.Addresses().Get(ctx, *key); err != nil {
|
||||
t.Errorf("Addresses().Get(%v, %v) = %v, %v; want nil", ctx, key, obj, err)
|
||||
}
|
||||
// List across versions.
|
||||
want := map[string]bool{"key-alpha": true, "key-beta": true, "key-ga": true}
|
||||
{
|
||||
objs, err := mock.AlphaAddresses().List(ctx, region, filter.None)
|
||||
if err != nil {
|
||||
t.Errorf("AlphaAddresses().List(%v, %v, %v) = %v, %v; want _, nil", ctx, region, filter.None, objs, err)
|
||||
} else {
|
||||
got := map[string]bool{}
|
||||
for _, obj := range objs {
|
||||
got[obj.Name] = true
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("AlphaAddresses().List(); got %+v, want %+v", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
objs, err := mock.BetaAddresses().List(ctx, region, filter.None)
|
||||
if err != nil {
|
||||
t.Errorf("BetaAddresses().List(%v, %v, %v) = %v, %v; want _, nil", ctx, region, filter.None, objs, err)
|
||||
} else {
|
||||
got := map[string]bool{}
|
||||
for _, obj := range objs {
|
||||
got[obj.Name] = true
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("AlphaAddresses().List(); got %+v, want %+v", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
objs, err := mock.Addresses().List(ctx, region, filter.None)
|
||||
if err != nil {
|
||||
t.Errorf("Addresses().List(%v, %v, %v) = %v, %v; want _, nil", ctx, region, filter.None, objs, err)
|
||||
} else {
|
||||
got := map[string]bool{}
|
||||
for _, obj := range objs {
|
||||
got[obj.Name] = true
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("AlphaAddresses().List(); got %+v, want %+v", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Delete across versions.
|
||||
if err := mock.AlphaAddresses().Delete(ctx, *keyAlpha); err != nil {
|
||||
t.Errorf("AlphaAddresses().Delete(%v, %v) = %v; want nil", ctx, key, err)
|
||||
}
|
||||
if err := mock.BetaAddresses().Delete(ctx, *keyBeta); err != nil {
|
||||
t.Errorf("BetaAddresses().Delete(%v, %v) = %v; want nil", ctx, key, err)
|
||||
}
|
||||
if err := mock.Addresses().Delete(ctx, *keyGA); err != nil {
|
||||
t.Errorf("Addresses().Delete(%v, %v) = %v; want nil", ctx, key, err)
|
||||
}
|
||||
// Delete not found.
|
||||
if err := mock.AlphaAddresses().Delete(ctx, *keyAlpha); err == nil {
|
||||
t.Errorf("AlphaAddresses().Delete(%v, %v) = nil; want error", ctx, key)
|
||||
}
|
||||
if err := mock.BetaAddresses().Delete(ctx, *keyBeta); err == nil {
|
||||
t.Errorf("BetaAddresses().Delete(%v, %v) = nil; want error", ctx, key)
|
||||
}
|
||||
if err := mock.Addresses().Delete(ctx, *keyGA); err == nil {
|
||||
t.Errorf("Addresses().Delete(%v, %v) = nil; want error", ctx, key)
|
||||
}
|
||||
}
|
142
pkg/cloudprovider/providers/gce/cloud/op.go
Normal file
142
pkg/cloudprovider/providers/gce/cloud/op.go
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
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 cloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
alpha "google.golang.org/api/compute/v0.alpha"
|
||||
beta "google.golang.org/api/compute/v0.beta"
|
||||
ga "google.golang.org/api/compute/v1"
|
||||
|
||||
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/meta"
|
||||
)
|
||||
|
||||
// operation is a GCE operation that can be watied on.
|
||||
type operation interface {
|
||||
// isDone queries GCE for the done status. This call can block.
|
||||
isDone(ctx context.Context) (bool, error)
|
||||
// rateLimitKey returns the rate limit key to use for the given operation.
|
||||
// This rate limit will govern how fast the server will be polled for
|
||||
// operation completion status.
|
||||
rateLimitKey() *RateLimitKey
|
||||
}
|
||||
|
||||
type gaOperation struct {
|
||||
s *Service
|
||||
op *ga.Operation
|
||||
projectID string
|
||||
}
|
||||
|
||||
func (o *gaOperation) isDone(ctx context.Context) (bool, error) {
|
||||
var (
|
||||
op *ga.Operation
|
||||
err error
|
||||
)
|
||||
|
||||
switch {
|
||||
case o.op.Region != "":
|
||||
op, err = o.s.GA.RegionOperations.Get(o.projectID, o.op.Region, o.op.Name).Context(ctx).Do()
|
||||
case o.op.Zone != "":
|
||||
op, err = o.s.GA.ZoneOperations.Get(o.projectID, o.op.Zone, o.op.Name).Context(ctx).Do()
|
||||
default:
|
||||
op, err = o.s.GA.GlobalOperations.Get(o.projectID, o.op.Name).Context(ctx).Do()
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return op != nil && op.Status == "DONE", nil
|
||||
}
|
||||
|
||||
func (o *gaOperation) rateLimitKey() *RateLimitKey {
|
||||
return &RateLimitKey{
|
||||
ProjectID: o.projectID,
|
||||
Operation: "Get",
|
||||
Service: "Operations",
|
||||
Version: meta.VersionGA,
|
||||
}
|
||||
}
|
||||
|
||||
type alphaOperation struct {
|
||||
s *Service
|
||||
op *alpha.Operation
|
||||
projectID string
|
||||
}
|
||||
|
||||
func (o *alphaOperation) isDone(ctx context.Context) (bool, error) {
|
||||
var (
|
||||
op *alpha.Operation
|
||||
err error
|
||||
)
|
||||
|
||||
switch {
|
||||
case o.op.Region != "":
|
||||
op, err = o.s.Alpha.RegionOperations.Get(o.projectID, o.op.Region, o.op.Name).Context(ctx).Do()
|
||||
case o.op.Zone != "":
|
||||
op, err = o.s.Alpha.ZoneOperations.Get(o.projectID, o.op.Zone, o.op.Name).Context(ctx).Do()
|
||||
default:
|
||||
op, err = o.s.Alpha.GlobalOperations.Get(o.projectID, o.op.Name).Context(ctx).Do()
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return op != nil && op.Status == "DONE", nil
|
||||
}
|
||||
|
||||
func (o *alphaOperation) rateLimitKey() *RateLimitKey {
|
||||
return &RateLimitKey{
|
||||
ProjectID: o.projectID,
|
||||
Operation: "Get",
|
||||
Service: "Operations",
|
||||
Version: meta.VersionAlpha,
|
||||
}
|
||||
}
|
||||
|
||||
type betaOperation struct {
|
||||
s *Service
|
||||
op *beta.Operation
|
||||
projectID string
|
||||
}
|
||||
|
||||
func (o *betaOperation) isDone(ctx context.Context) (bool, error) {
|
||||
var (
|
||||
op *beta.Operation
|
||||
err error
|
||||
)
|
||||
|
||||
switch {
|
||||
case o.op.Region != "":
|
||||
op, err = o.s.Beta.RegionOperations.Get(o.projectID, o.op.Region, o.op.Name).Context(ctx).Do()
|
||||
case o.op.Zone != "":
|
||||
op, err = o.s.Beta.ZoneOperations.Get(o.projectID, o.op.Zone, o.op.Name).Context(ctx).Do()
|
||||
default:
|
||||
op, err = o.s.Beta.GlobalOperations.Get(o.projectID, o.op.Name).Context(ctx).Do()
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return op != nil && op.Status == "DONE", nil
|
||||
}
|
||||
|
||||
func (o *betaOperation) rateLimitKey() *RateLimitKey {
|
||||
return &RateLimitKey{
|
||||
ProjectID: o.projectID,
|
||||
Operation: "Get",
|
||||
Service: "Operations",
|
||||
Version: meta.VersionBeta,
|
||||
}
|
||||
}
|
45
pkg/cloudprovider/providers/gce/cloud/project.go
Normal file
45
pkg/cloudprovider/providers/gce/cloud/project.go
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
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 cloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/meta"
|
||||
)
|
||||
|
||||
// ProjectRouter routes service calls to the appropriate GCE project.
|
||||
type ProjectRouter interface {
|
||||
// ProjectID returns the project ID (non-numeric) to be used for a call
|
||||
// to an API (version,service). Example tuples: ("ga", "ForwardingRules"),
|
||||
// ("alpha", "GlobalAddresses").
|
||||
//
|
||||
// This allows for plumbing different service calls to the appropriate
|
||||
// project, for instance, networking services to a separate project
|
||||
// than instance management.
|
||||
ProjectID(ctx context.Context, version meta.Version, service string) string
|
||||
}
|
||||
|
||||
// SingleProjectRouter routes all service calls to the same project ID.
|
||||
type SingleProjectRouter struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// ProjectID returns the project ID to be used for a call to the API.
|
||||
func (r *SingleProjectRouter) ProjectID(ctx context.Context, version meta.Version, service string) string {
|
||||
return r.ID
|
||||
}
|
68
pkg/cloudprovider/providers/gce/cloud/ratelimit.go
Normal file
68
pkg/cloudprovider/providers/gce/cloud/ratelimit.go
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
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 cloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/meta"
|
||||
)
|
||||
|
||||
// RateLimitKey is a key identifying the operation to be rate limited. The rate limit
|
||||
// queue will be determined based on the contents of RateKey.
|
||||
type RateLimitKey struct {
|
||||
// ProjectID is the non-numeric ID of the project.
|
||||
ProjectID string
|
||||
// Operation is the specific method being invoked (e.g. "Get", "List").
|
||||
Operation string
|
||||
// Version is the API version of the call.
|
||||
Version meta.Version
|
||||
// Service is the service being invoked (e.g. "Firewalls", "BackendServices")
|
||||
Service string
|
||||
}
|
||||
|
||||
// RateLimiter is the interface for a rate limiting policy.
|
||||
type RateLimiter interface {
|
||||
// Accept uses the RateLimitKey to derive a sleep time for the calling
|
||||
// goroutine. This call will block until the operation is ready for
|
||||
// execution.
|
||||
//
|
||||
// Accept returns an error if the given context ctx was canceled
|
||||
// while waiting for acceptance into the queue.
|
||||
Accept(ctx context.Context, key *RateLimitKey) error
|
||||
}
|
||||
|
||||
// NopRateLimiter is a rate limiter that performs no rate limiting.
|
||||
type NopRateLimiter struct {
|
||||
}
|
||||
|
||||
// Accept the operation to be rate limited.
|
||||
func (*NopRateLimiter) Accept(ctx context.Context, key *RateLimitKey) error {
|
||||
// Rate limit polling of the Operation status to avoid hammering GCE
|
||||
// for the status of an operation.
|
||||
const pollTime = time.Duration(1) * time.Second
|
||||
if key.Operation == "Get" && key.Service == "Operations" {
|
||||
select {
|
||||
case <-time.NewTimer(pollTime).C:
|
||||
break
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
79
pkg/cloudprovider/providers/gce/cloud/service.go
Normal file
79
pkg/cloudprovider/providers/gce/cloud/service.go
Normal 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 cloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
alpha "google.golang.org/api/compute/v0.alpha"
|
||||
beta "google.golang.org/api/compute/v0.beta"
|
||||
ga "google.golang.org/api/compute/v1"
|
||||
)
|
||||
|
||||
// Service is the top-level adapter for all of the different compute API
|
||||
// versions.
|
||||
type Service struct {
|
||||
GA *ga.Service
|
||||
Alpha *alpha.Service
|
||||
Beta *beta.Service
|
||||
ProjectRouter ProjectRouter
|
||||
RateLimiter RateLimiter
|
||||
}
|
||||
|
||||
// wrapOperation wraps a GCE anyOP in a version generic operation type.
|
||||
func (g *Service) wrapOperation(anyOp interface{}) (operation, error) {
|
||||
switch o := anyOp.(type) {
|
||||
case *ga.Operation:
|
||||
r, err := ParseResourceURL(o.SelfLink)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &gaOperation{g, o, r.ProjectID}, nil
|
||||
case *alpha.Operation:
|
||||
r, err := ParseResourceURL(o.SelfLink)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &alphaOperation{g, o, r.ProjectID}, nil
|
||||
case *beta.Operation:
|
||||
r, err := ParseResourceURL(o.SelfLink)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &betaOperation{g, o, r.ProjectID}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid type %T", anyOp)
|
||||
}
|
||||
}
|
||||
|
||||
// WaitForCompletion of a long running operation. This will poll the state of
|
||||
// GCE for the completion status of the given operation. genericOp can be one
|
||||
// of alpha, beta, ga Operation types.
|
||||
func (g *Service) WaitForCompletion(ctx context.Context, genericOp interface{}) error {
|
||||
op, err := g.wrapOperation(genericOp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for done, err := op.isDone(ctx); !done; done, err = op.isDone(ctx) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.RateLimiter.Accept(ctx, op.rateLimitKey())
|
||||
}
|
||||
return nil
|
||||
}
|
167
pkg/cloudprovider/providers/gce/cloud/utils.go
Normal file
167
pkg/cloudprovider/providers/gce/cloud/utils.go
Normal 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 cloud
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/meta"
|
||||
)
|
||||
|
||||
const (
|
||||
gaPrefix = "https://www.googleapis.com/compute/v1/"
|
||||
alphaPrefix = "https://www.googleapis.com/compute/alpha/"
|
||||
betaPrefix = "https://www.googleapis.com/compute/beta/"
|
||||
)
|
||||
|
||||
var (
|
||||
allPrefixes = []string{gaPrefix, alphaPrefix, betaPrefix}
|
||||
)
|
||||
|
||||
// ResourceID identifies a GCE resource as parsed from compute resource URL.
|
||||
type ResourceID struct {
|
||||
ProjectID string
|
||||
Resource string
|
||||
Key *meta.Key
|
||||
}
|
||||
|
||||
// Equal returns true if two resource IDs are equal.
|
||||
func (r *ResourceID) Equal(other *ResourceID) bool {
|
||||
if r.ProjectID != other.ProjectID || r.Resource != other.Resource {
|
||||
return false
|
||||
}
|
||||
if r.Key != nil && other.Key != nil {
|
||||
return *r.Key == *other.Key
|
||||
}
|
||||
if r.Key == nil && other.Key == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ParseResourceURL parses resource URLs of the following formats:
|
||||
//
|
||||
// projects/<proj>/global/<res>/<name>
|
||||
// projects/<proj>/regions/<region>/<res>/<name>
|
||||
// projects/<proj>/zones/<zone>/<res>/<name>
|
||||
// [https://www.googleapis.com/compute/<ver>]/projects/<proj>/global/<res>/<name>
|
||||
// [https://www.googleapis.com/compute/<ver>]/projects/<proj>/regions/<region>/<res>/<name>
|
||||
// [https://www.googleapis.com/compute/<ver>]/projects/<proj>/zones/<zone>/<res>/<name>
|
||||
func ParseResourceURL(url string) (*ResourceID, error) {
|
||||
errNotValid := fmt.Errorf("%q is not a valid resource URL", url)
|
||||
|
||||
// Remove the "https://..." prefix if present
|
||||
for _, prefix := range allPrefixes {
|
||||
if strings.HasPrefix(url, prefix) {
|
||||
if len(url) < len(prefix) {
|
||||
return nil, errNotValid
|
||||
}
|
||||
url = url[len(prefix):]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
parts := strings.Split(url, "/")
|
||||
if len(parts) < 2 || parts[0] != "projects" {
|
||||
return nil, errNotValid
|
||||
}
|
||||
|
||||
ret := &ResourceID{ProjectID: parts[1]}
|
||||
if len(parts) == 2 {
|
||||
ret.Resource = "projects"
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
if len(parts) < 4 {
|
||||
return nil, errNotValid
|
||||
}
|
||||
|
||||
if len(parts) == 4 {
|
||||
switch parts[2] {
|
||||
case "regions":
|
||||
ret.Resource = "regions"
|
||||
ret.Key = meta.GlobalKey(parts[3])
|
||||
return ret, nil
|
||||
case "zones":
|
||||
ret.Resource = "zones"
|
||||
ret.Key = meta.GlobalKey(parts[3])
|
||||
return ret, nil
|
||||
default:
|
||||
return nil, errNotValid
|
||||
}
|
||||
}
|
||||
|
||||
switch parts[2] {
|
||||
case "global":
|
||||
if len(parts) != 5 {
|
||||
return nil, errNotValid
|
||||
}
|
||||
ret.Resource = parts[3]
|
||||
ret.Key = meta.GlobalKey(parts[4])
|
||||
return ret, nil
|
||||
case "regions":
|
||||
if len(parts) != 6 {
|
||||
return nil, errNotValid
|
||||
}
|
||||
ret.Resource = parts[4]
|
||||
ret.Key = meta.RegionalKey(parts[5], parts[3])
|
||||
return ret, nil
|
||||
case "zones":
|
||||
if len(parts) != 6 {
|
||||
return nil, errNotValid
|
||||
}
|
||||
ret.Resource = parts[4]
|
||||
ret.Key = meta.ZonalKey(parts[5], parts[3])
|
||||
return ret, nil
|
||||
}
|
||||
return nil, errNotValid
|
||||
}
|
||||
|
||||
func copyViaJSON(dest, src interface{}) error {
|
||||
bytes, err := json.Marshal(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(bytes, dest)
|
||||
}
|
||||
|
||||
// SelfLink returns the self link URL for the given object.
|
||||
func SelfLink(ver meta.Version, project, resource string, key meta.Key) string {
|
||||
var prefix string
|
||||
switch ver {
|
||||
case meta.VersionAlpha:
|
||||
prefix = alphaPrefix
|
||||
case meta.VersionBeta:
|
||||
prefix = betaPrefix
|
||||
case meta.VersionGA:
|
||||
prefix = gaPrefix
|
||||
default:
|
||||
prefix = "invalid-prefix"
|
||||
}
|
||||
|
||||
switch key.Type() {
|
||||
case meta.Zonal:
|
||||
return fmt.Sprintf("%sprojects/%s/zones/%s/%s/%s", prefix, project, key.Zone, resource, key.Name)
|
||||
case meta.Regional:
|
||||
return fmt.Sprintf("%sprojects/%s/regions/%s/%s/%s", prefix, project, key.Region, resource, key.Name)
|
||||
case meta.Global:
|
||||
return fmt.Sprintf("%sprojects/%s/%s/%s", prefix, project, resource, key.Name)
|
||||
}
|
||||
return "invalid-self-link"
|
||||
}
|
197
pkg/cloudprovider/providers/gce/cloud/utils_test.go
Normal file
197
pkg/cloudprovider/providers/gce/cloud/utils_test.go
Normal file
@ -0,0 +1,197 @@
|
||||
/*
|
||||
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 cloud
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/meta"
|
||||
)
|
||||
|
||||
func TestParseResourceURL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range []struct {
|
||||
in string
|
||||
r *ResourceID
|
||||
}{
|
||||
{
|
||||
"https://www.googleapis.com/compute/v1/projects/some-gce-project",
|
||||
&ResourceID{"some-gce-project", "projects", nil},
|
||||
},
|
||||
{
|
||||
"https://www.googleapis.com/compute/v1/projects/some-gce-project/regions/us-central1",
|
||||
&ResourceID{"some-gce-project", "regions", meta.GlobalKey("us-central1")},
|
||||
},
|
||||
{
|
||||
"https://www.googleapis.com/compute/v1/projects/some-gce-project/zones/us-central1-b",
|
||||
&ResourceID{"some-gce-project", "zones", meta.GlobalKey("us-central1-b")},
|
||||
},
|
||||
{
|
||||
"https://www.googleapis.com/compute/v1/projects/some-gce-project/global/operations/operation-1513289952196-56054460af5a0-b1dae0c3-9bbf9dbf",
|
||||
&ResourceID{"some-gce-project", "operations", meta.GlobalKey("operation-1513289952196-56054460af5a0-b1dae0c3-9bbf9dbf")},
|
||||
},
|
||||
{
|
||||
"https://www.googleapis.com/compute/alpha/projects/some-gce-project/regions/us-central1/addresses/my-address",
|
||||
&ResourceID{"some-gce-project", "addresses", meta.RegionalKey("my-address", "us-central1")},
|
||||
},
|
||||
{
|
||||
"https://www.googleapis.com/compute/v1/projects/some-gce-project/zones/us-central1-c/instances/instance-1",
|
||||
&ResourceID{"some-gce-project", "instances", meta.ZonalKey("instance-1", "us-central1-c")},
|
||||
},
|
||||
{
|
||||
"projects/some-gce-project",
|
||||
&ResourceID{"some-gce-project", "projects", nil},
|
||||
},
|
||||
{
|
||||
"projects/some-gce-project/regions/us-central1",
|
||||
&ResourceID{"some-gce-project", "regions", meta.GlobalKey("us-central1")},
|
||||
},
|
||||
{
|
||||
"projects/some-gce-project/zones/us-central1-b",
|
||||
&ResourceID{"some-gce-project", "zones", meta.GlobalKey("us-central1-b")},
|
||||
},
|
||||
{
|
||||
"projects/some-gce-project/global/operations/operation-1513289952196-56054460af5a0-b1dae0c3-9bbf9dbf",
|
||||
&ResourceID{"some-gce-project", "operations", meta.GlobalKey("operation-1513289952196-56054460af5a0-b1dae0c3-9bbf9dbf")},
|
||||
},
|
||||
{
|
||||
"projects/some-gce-project/regions/us-central1/addresses/my-address",
|
||||
&ResourceID{"some-gce-project", "addresses", meta.RegionalKey("my-address", "us-central1")},
|
||||
},
|
||||
{
|
||||
"projects/some-gce-project/zones/us-central1-c/instances/instance-1",
|
||||
&ResourceID{"some-gce-project", "instances", meta.ZonalKey("instance-1", "us-central1-c")},
|
||||
},
|
||||
} {
|
||||
r, err := ParseResourceURL(tc.in)
|
||||
if err != nil {
|
||||
t.Errorf("ParseResourceURL(%q) = %+v, %v; want _, nil", tc.in, r, err)
|
||||
continue
|
||||
}
|
||||
if !r.Equal(tc.r) {
|
||||
t.Errorf("ParseResourceURL(%q) = %+v, nil; want %+v, nil", tc.in, r, tc.r)
|
||||
}
|
||||
}
|
||||
// Malformed URLs.
|
||||
for _, tc := range []string{
|
||||
"",
|
||||
"/",
|
||||
"/a",
|
||||
"/a/b",
|
||||
"/a/b/c",
|
||||
"/a/b/c/d",
|
||||
"/a/b/c/d/e",
|
||||
"/a/b/c/d/e/f",
|
||||
"https://www.googleapis.com/compute/v1/projects/some-gce-project/global",
|
||||
"projects/some-gce-project/global",
|
||||
"projects/some-gce-project/global/foo/bar/baz",
|
||||
"projects/some-gce-project/zones/us-central1-c/res",
|
||||
"projects/some-gce-project/zones/us-central1-c/res/name/extra",
|
||||
"https://www.googleapis.com/compute/gamma/projects/some-gce-project/global/addresses/name",
|
||||
} {
|
||||
r, err := ParseResourceURL(tc)
|
||||
if err == nil {
|
||||
t.Errorf("ParseResourceURL(%q) = %+v, %v, want _, error", tc, r, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type A struct {
|
||||
A, B, C string
|
||||
}
|
||||
|
||||
type B struct {
|
||||
A, B, D string
|
||||
}
|
||||
|
||||
type E struct{}
|
||||
|
||||
func (*E) MarshalJSON() ([]byte, error) {
|
||||
return nil, errors.New("injected error")
|
||||
}
|
||||
|
||||
func TestCopyVisJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var b B
|
||||
srcA := &A{"aa", "bb", "cc"}
|
||||
err := copyViaJSON(&b, srcA)
|
||||
if err != nil {
|
||||
t.Errorf(`copyViaJSON(&b, %+v) = %v, want nil`, srcA, err)
|
||||
} else {
|
||||
expectedB := B{"aa", "bb", ""}
|
||||
if b != expectedB {
|
||||
t.Errorf("b == %+v, want %+v", b, expectedB)
|
||||
}
|
||||
}
|
||||
|
||||
var a A
|
||||
srcB := &B{"aaa", "bbb", "ccc"}
|
||||
err = copyViaJSON(&a, srcB)
|
||||
if err != nil {
|
||||
t.Errorf(`copyViaJSON(&a, %+v) = %v, want nil`, srcB, err)
|
||||
} else {
|
||||
expectedA := A{"aaa", "bbb", ""}
|
||||
if a != expectedA {
|
||||
t.Errorf("a == %+v, want %+v", a, expectedA)
|
||||
}
|
||||
}
|
||||
|
||||
if err := copyViaJSON(&a, &E{}); err == nil {
|
||||
t.Errorf("copyViaJSON(&a, &E{}) = nil, want error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelfLink(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range []struct {
|
||||
ver meta.Version
|
||||
project string
|
||||
resource string
|
||||
key meta.Key
|
||||
want string
|
||||
}{
|
||||
{
|
||||
meta.VersionAlpha,
|
||||
"proj1",
|
||||
"addresses",
|
||||
*meta.RegionalKey("key1", "us-central1"),
|
||||
"https://www.googleapis.com/compute/alpha/projects/proj1/regions/us-central1/addresses/key1",
|
||||
},
|
||||
{
|
||||
meta.VersionBeta,
|
||||
"proj3",
|
||||
"disks",
|
||||
*meta.ZonalKey("key2", "us-central1-b"),
|
||||
"https://www.googleapis.com/compute/beta/projects/proj3/zones/us-central1-b/disks/key2",
|
||||
},
|
||||
{
|
||||
meta.VersionGA,
|
||||
"proj4",
|
||||
"urlMaps",
|
||||
*meta.GlobalKey("key3"),
|
||||
"https://www.googleapis.com/compute/v1/projects/proj4/urlMaps/key3",
|
||||
},
|
||||
} {
|
||||
if link := SelfLink(tc.ver, tc.project, tc.resource, tc.key); link != tc.want {
|
||||
t.Errorf("SelfLink(%v, %q, %q, %v) = %v, want %q", tc.ver, tc.project, tc.resource, tc.key, link, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user