Add vendored version of the cloud libraries

pull/564/head
Bowei Du 2018-12-13 17:32:20 -08:00
parent 83ba05a002
commit 295a797047
18 changed files with 18461 additions and 0 deletions

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@ -0,0 +1,55 @@
/*
Copyright 2018 Google LLC
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
https://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 (
"strings"
)
// NetworkTier represents the Network Service Tier used by a resource
type NetworkTier string
// LbScheme represents the possible types of load balancers
type LbScheme string
const (
NetworkTierStandard NetworkTier = "Standard"
NetworkTierPremium NetworkTier = "Premium"
NetworkTierDefault NetworkTier = NetworkTierPremium
SchemeExternal LbScheme = "EXTERNAL"
SchemeInternal LbScheme = "INTERNAL"
)
// ToGCEValue converts NetworkTier to a string that we can populate the
// NetworkTier field of GCE objects, including ForwardingRules and Addresses.
func (n NetworkTier) ToGCEValue() string {
return strings.ToUpper(string(n))
}
// NetworkTierGCEValueToType converts the value of the NetworkTier field of a
// GCE object to the NetworkTier type.
func NetworkTierGCEValueToType(s string) NetworkTier {
switch s {
case NetworkTierStandard.ToGCEValue():
return NetworkTierStandard
case NetworkTierPremium.ToGCEValue():
return NetworkTierPremium
default:
return NetworkTier(s)
}
}

View File

@ -0,0 +1,31 @@
/*
Copyright 2018 Google LLC
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
https://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"
)
const (
defaultCallTimeout = 1 * time.Hour
)
// ContextWithCallTimeout returns a context with a default timeout, used for generated client calls.
func ContextWithCallTimeout() (context.Context, context.CancelFunc) {
return context.WithTimeout(context.Background(), defaultCallTimeout)
}

View File

@ -0,0 +1,117 @@
/*
Copyright 2018 Google LLC
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
https://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.
// }
//
// Update generated codes
//
// Run hack/update-cloudprovider-gce.sh to update the generated codes.
//
package cloud

View File

@ -0,0 +1,303 @@
/*
Copyright 2018 Google LLC
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
https://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"
"k8s.io/klog"
)
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)
klog.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 {
klog.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)
}

View File

@ -0,0 +1,99 @@
/*
Copyright 2018 Google LLC
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
https://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"
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"
compute "google.golang.org/api/compute/v1"
"google.golang.org/api/googleapi"
)
// 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)
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
/*
Copyright 2018 Google LLC
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
https://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

View File

@ -0,0 +1,108 @@
/*
Copyright 2018 Google LLC
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
https://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"
"regexp"
)
// 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"
)
var (
// locationRegexp is the format of regions/zone names in GCE.
locationRegexp = regexp.MustCompile("^[a-z](?:[-a-z0-9]+)?$")
)
// 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() bool {
if k.Zone != "" && k.Region != "" {
return false
}
switch {
case k.Region != "":
return locationRegexp.Match([]byte(k.Region))
case k.Zone != "":
return locationRegexp.Match([]byte(k.Zone))
}
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
}

View File

@ -0,0 +1,440 @@
/*
Copyright 2018 Google LLC
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
https://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",
"Patch",
"Update",
},
},
{
Object: "BackendService",
Service: "BackendServices",
Resource: "backendServices",
version: VersionBeta,
keyType: Global,
serviceType: reflect.TypeOf(&beta.BackendServicesService{}),
additionalMethods: []string{
"Update",
"SetSecurityPolicy",
},
},
{
Object: "BackendService",
Service: "BackendServices",
Resource: "backendServices",
version: VersionAlpha,
keyType: Global,
serviceType: reflect.TypeOf(&alpha.BackendServicesService{}),
additionalMethods: []string{
"Update",
"SetSecurityPolicy",
},
},
{
Object: "BackendService",
Service: "RegionBackendServices",
Resource: "backendServices",
version: VersionGA,
keyType: Regional,
serviceType: reflect.TypeOf(&ga.RegionBackendServicesService{}),
additionalMethods: []string{
"GetHealth",
"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{}),
additionalMethods: []string{
"Resize",
},
},
{
Object: "Disk",
Service: "RegionDisks",
Resource: "disks",
version: VersionGA,
keyType: Regional,
serviceType: reflect.TypeOf(&ga.RegionDisksService{}),
additionalMethods: []string{
"Resize",
},
},
{
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: "HealthCheck",
Service: "HealthChecks",
Resource: "healthChecks",
version: VersionBeta,
keyType: Global,
serviceType: reflect.TypeOf(&beta.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",
"UpdateNetworkInterface",
},
},
{
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",
"ListNetworkEndpoints",
},
options: AggregatedList,
},
{
Object: "NetworkEndpointGroup",
Service: "NetworkEndpointGroups",
Resource: "networkEndpointGroups",
version: VersionBeta,
keyType: Zonal,
serviceType: reflect.TypeOf(&beta.NetworkEndpointGroupsService{}),
additionalMethods: []string{
"AttachNetworkEndpoints",
"DetachNetworkEndpoints",
"ListNetworkEndpoints",
},
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: "SecurityPolicy",
Service: "SecurityPolicies",
Resource: "securityPolicies",
version: VersionBeta,
keyType: Global,
serviceType: reflect.TypeOf(&beta.SecurityPoliciesService{}),
additionalMethods: []string{
"AddRule",
"GetRule",
"Patch",
"PatchRule",
"RemoveRule",
},
},
{
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{}),
},
}

View File

@ -0,0 +1,337 @@
/*
Copyright 2018 Google LLC
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
https://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"
)
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{
ServiceInfo: s,
m: m,
kind: MethodOperation,
ReturnType: "",
}
ret.init()
return ret
}
// MethodKind is the type of method that we are generated code for.
type MethodKind int
const (
// MethodOperation is a long running method that returns an operation.
MethodOperation MethodKind = iota
// MethodGet is a method that immediately returns some data.
MethodGet MethodKind = iota
// MethodPaged is a method that returns a paged set of data.
MethodPaged MethodKind = iota
)
// Method is used to generate the calling code for non-standard methods.
type Method struct {
*ServiceInfo
m reflect.Method
kind MethodKind
// ReturnType is the return type for the method.
ReturnType string
// ItemType is the type of the individual elements returns from a
// Pages() call. This is only applicable for MethodPaged kind.
ItemType string
}
// IsOperation is true if the method is an Operation.
func (m *Method) IsOperation() bool {
return m.kind == MethodOperation
}
// IsPaged is true if the method paged.
func (m *Method) IsPaged() bool {
return m.kind == MethodPaged
}
// IsGet is true if the method simple get.
func (m *Method) IsGet() bool {
return m.kind == MethodGet
}
// argsSkip is the number of arguments to skip when generating the
// synthesized method.
func (m *Method) argsSkip() int {
switch m.keyType {
case Zonal:
return 4
case Regional:
return 4
case Global:
return 3
}
panic(fmt.Errorf("invalid KeyType %v", m.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 (m *Method) args(skip int, nameArgs bool, prefix []string) []string {
var args []*arg
fType := m.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. This performs some rudimentary static checking as well as
// determines the kind of method by looking at the shape (method signature) of
// the object.
func (m *Method) init() {
fType := m.m.Func.Type()
if fType.NumIn() < m.argsSkip() {
err := fmt.Errorf("method %q.%q, arity = %d which is less than required (< %d)",
m.Service, m.Name(), fType.NumIn(), m.argsSkip())
panic(err)
}
// Skipped args should all be string (they will be projectID, zone, region etc).
for i := 1; i < m.argsSkip(); i++ {
if fType.In(i).Kind() != reflect.String {
panic(fmt.Errorf("method %q.%q: skipped args can only be strings", m.Service, m.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",
m.Service, m.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",
m.Service, m.Name(), returnTypeName))
}
_, hasPages := returnType.MethodByName("Pages")
// Do() method must return (*T, error).
switch doMethod.Func.Type().NumOut() {
case 2:
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)",
m.Service, m.Name(), returnTypeName, out0))
}
m.ReturnType = out0.Elem().Name()
switch {
case out0.Elem().Name() == "Operation":
m.kind = MethodOperation
case hasPages:
m.kind = MethodPaged
// Pages() returns a xxxList that has the actual list
// of objects in the xxxList.Items field.
listType := out0.Elem()
itemsField, ok := listType.FieldByName("Items")
if !ok {
panic(fmt.Errorf("method %q.%q: paged return type %q does not have a .Items field", m.Service, m.Name(), listType.Name()))
}
// itemsField will be a []*ItemType. Dereference to
// extract the ItemType.
itemsType := itemsField.Type
if itemsType.Kind() != reflect.Slice && itemsType.Elem().Kind() != reflect.Ptr {
panic(fmt.Errorf("method %q.%q: paged return type %q.Items is not an array of pointers", m.Service, m.Name(), listType.Name()))
}
m.ItemType = itemsType.Elem().Elem().Name()
default:
m.kind = MethodGet
}
// 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'",
m.Service, m.Name(), returnTypeName))
}
break
default:
panic(fmt.Errorf("method %q.%q: %q Do() return type is not handled by the generator",
m.Service, m.Name(), returnTypeName))
}
}
// Name is the name of the method.
func (m *Method) Name() string {
return m.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 (m *Method) CallArgs() string {
var args []string
for i := m.argsSkip(); i < m.m.Func.Type().NumIn(); i++ {
args = append(args, fmt.Sprintf("arg%d", i-m.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 (m *Method) MockHookName() string {
return m.m.Name + "Hook"
}
// MockHook is the definition of the hook function.
func (m *Method) MockHook() string {
args := m.args(m.argsSkip(), false, []string{
"context.Context",
"*meta.Key",
})
if m.kind == MethodPaged {
args = append(args, "*filter.F")
}
args = append(args, fmt.Sprintf("*%s", m.MockWrapType()))
switch m.kind {
case MethodOperation:
return fmt.Sprintf("%v func(%v) error", m.MockHookName(), strings.Join(args, ", "))
case MethodGet:
return fmt.Sprintf("%v func(%v) (*%v.%v, error)", m.MockHookName(), strings.Join(args, ", "), m.Version(), m.ReturnType)
case MethodPaged:
return fmt.Sprintf("%v func(%v) ([]*%v.%v, error)", m.MockHookName(), strings.Join(args, ", "), m.Version(), m.ItemType)
default:
panic(fmt.Errorf("invalid method kind: %v", m.kind))
}
}
// FcnArgs is the function signature for the definition of the method.
func (m *Method) FcnArgs() string {
args := m.args(m.argsSkip(), true, []string{
"ctx context.Context",
"key *meta.Key",
})
if m.kind == MethodPaged {
args = append(args, "fl *filter.F")
}
switch m.kind {
case MethodOperation:
return fmt.Sprintf("%v(%v) error", m.m.Name, strings.Join(args, ", "))
case MethodGet:
return fmt.Sprintf("%v(%v) (*%v.%v, error)", m.m.Name, strings.Join(args, ", "), m.Version(), m.ReturnType)
case MethodPaged:
return fmt.Sprintf("%v(%v) ([]*%v.%v, error)", m.m.Name, strings.Join(args, ", "), m.Version(), m.ItemType)
default:
panic(fmt.Errorf("invalid method kind: %v", m.kind))
}
}
// InterfaceFunc is the function declaration of the method in the interface.
func (m *Method) InterfaceFunc() string {
args := []string{
"context.Context",
"*meta.Key",
}
args = m.args(m.argsSkip(), false, args)
if m.kind == MethodPaged {
args = append(args, "*filter.F")
}
switch m.kind {
case MethodOperation:
return fmt.Sprintf("%v(%v) error", m.m.Name, strings.Join(args, ", "))
case MethodGet:
return fmt.Sprintf("%v(%v) (*%v.%v, error)", m.m.Name, strings.Join(args, ", "), m.Version(), m.ReturnType)
case MethodPaged:
return fmt.Sprintf("%v(%v) ([]*%v.%v, error)", m.m.Name, strings.Join(args, ", "), m.Version(), m.ItemType)
default:
panic(fmt.Errorf("invalid method kind: %v", m.kind))
}
}

View File

@ -0,0 +1,300 @@
/*
Copyright 2018 Google LLC
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
https://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"
"sort"
)
// 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
}
// KeyIsProject is true if the key represents the project resource.
func (i *ServiceInfo) KeyIsProject() bool {
// Projects are a special resource for ResourceId because there is no 'key' value. This func
// is used by the generator to not accept a key parameter.
return i.Service == "Projects"
}
// 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 string belonging to the ServiceGroup.
func (sg *ServiceGroup) Service() string {
return sg.ServiceInfo().Service
}
// ServiceInfo returns any ServiceInfo object belonging to the ServiceGroup.
func (sg *ServiceGroup) ServiceInfo() *ServiceInfo {
switch {
case sg.GA != nil:
return sg.GA
case sg.Alpha != nil:
return sg.Alpha
case sg.Beta != nil:
return sg.Beta
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
// SortedServicesGroups is a slice of Servicegroup sorted by Service name.
var SortedServicesGroups []*ServiceGroup
func init() {
AllServicesByGroup = groupServices(AllServices)
for _, sg := range AllServicesByGroup {
SortedServicesGroups = append(SortedServicesGroups, sg)
}
sort.Slice(SortedServicesGroups, func(i, j int) bool {
return SortedServicesGroups[i].Service() < SortedServicesGroups[j].Service()
})
}

View File

@ -0,0 +1,640 @@
/*
Copyright 2018 Google LLC
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
https://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 mock encapsulates mocks for testing GCE provider functionality.
// These methods are used to override the mock objects' methods in order to
// intercept the standard processing and to add custom logic for test purposes.
//
// // Example usage:
// cloud := cloud.NewMockGCE()
// cloud.MockTargetPools.AddInstanceHook = mock.AddInstanceHook
package mock
import (
"context"
"encoding/json"
"fmt"
"net/http"
"sync"
cloud "github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud"
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/filter"
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"
alpha "google.golang.org/api/compute/v0.alpha"
beta "google.golang.org/api/compute/v0.beta"
ga "google.golang.org/api/compute/v1"
"google.golang.org/api/googleapi"
)
var (
// InUseError is a shared variable with error code StatusBadRequest for error verification.
InUseError = &googleapi.Error{Code: http.StatusBadRequest, Message: "It's being used by god."}
// InternalServerError is shared variable with error code StatusInternalServerError for error verification.
InternalServerError = &googleapi.Error{Code: http.StatusInternalServerError}
// UnauthorizedErr wraps a Google API error with code StatusForbidden.
UnauthorizedErr = &googleapi.Error{Code: http.StatusForbidden}
)
// gceObject is an abstraction of all GCE API object in go client
type gceObject interface {
MarshalJSON() ([]byte, error)
}
// AddInstanceHook mocks adding a Instance to MockTargetPools
func AddInstanceHook(ctx context.Context, key *meta.Key, req *ga.TargetPoolsAddInstanceRequest, m *cloud.MockTargetPools) error {
pool, err := m.Get(ctx, key)
if err != nil {
return &googleapi.Error{
Code: http.StatusNotFound,
Message: fmt.Sprintf("Key: %s was not found in TargetPools", key.String()),
}
}
for _, instance := range req.Instances {
pool.Instances = append(pool.Instances, instance.Instance)
}
return nil
}
// RemoveInstanceHook mocks removing a Instance from MockTargetPools
func RemoveInstanceHook(ctx context.Context, key *meta.Key, req *ga.TargetPoolsRemoveInstanceRequest, m *cloud.MockTargetPools) error {
pool, err := m.Get(ctx, key)
if err != nil {
return &googleapi.Error{
Code: http.StatusNotFound,
Message: fmt.Sprintf("Key: %s was not found in TargetPools", key.String()),
}
}
for _, instanceToRemove := range req.Instances {
for i, instance := range pool.Instances {
if instanceToRemove.Instance == instance {
// Delete instance from pool.Instances without preserving order
pool.Instances[i] = pool.Instances[len(pool.Instances)-1]
pool.Instances = pool.Instances[:len(pool.Instances)-1]
break
}
}
}
return nil
}
func convertAndInsertAlphaForwardingRule(key *meta.Key, obj gceObject, mRules map[meta.Key]*cloud.MockForwardingRulesObj, version meta.Version, projectID string) (bool, error) {
if !key.Valid() {
return true, fmt.Errorf("invalid GCE key (%+v)", key)
}
if _, ok := mRules[*key]; ok {
err := &googleapi.Error{
Code: http.StatusConflict,
Message: fmt.Sprintf("MockForwardingRule %v exists", key),
}
return true, err
}
enc, err := obj.MarshalJSON()
if err != nil {
return true, err
}
var fwdRule alpha.ForwardingRule
if err := json.Unmarshal(enc, &fwdRule); err != nil {
return true, err
}
// Set the default values for the Alpha fields.
if fwdRule.NetworkTier == "" {
fwdRule.NetworkTier = cloud.NetworkTierDefault.ToGCEValue()
}
fwdRule.Name = key.Name
if fwdRule.SelfLink == "" {
fwdRule.SelfLink = cloud.SelfLink(version, projectID, "forwardingRules", key)
}
mRules[*key] = &cloud.MockForwardingRulesObj{Obj: fwdRule}
return true, nil
}
// InsertFwdRuleHook mocks inserting a ForwardingRule. ForwardingRules are
// expected to default to Premium tier if no NetworkTier is specified.
func InsertFwdRuleHook(ctx context.Context, key *meta.Key, obj *ga.ForwardingRule, m *cloud.MockForwardingRules) (bool, error) {
m.Lock.Lock()
defer m.Lock.Unlock()
projectID := m.ProjectRouter.ProjectID(ctx, meta.VersionGA, "forwardingRules")
return convertAndInsertAlphaForwardingRule(key, obj, m.Objects, meta.VersionGA, projectID)
}
// InsertBetaFwdRuleHook mocks inserting a BetaForwardingRule.
func InsertBetaFwdRuleHook(ctx context.Context, key *meta.Key, obj *beta.ForwardingRule, m *cloud.MockForwardingRules) (bool, error) {
m.Lock.Lock()
defer m.Lock.Unlock()
projectID := m.ProjectRouter.ProjectID(ctx, meta.VersionBeta, "forwardingRules")
return convertAndInsertAlphaForwardingRule(key, obj, m.Objects, meta.VersionBeta, projectID)
}
// InsertAlphaFwdRuleHook mocks inserting an AlphaForwardingRule.
func InsertAlphaFwdRuleHook(ctx context.Context, key *meta.Key, obj *alpha.ForwardingRule, m *cloud.MockForwardingRules) (bool, error) {
m.Lock.Lock()
defer m.Lock.Unlock()
projectID := m.ProjectRouter.ProjectID(ctx, meta.VersionAlpha, "forwardingRules")
return convertAndInsertAlphaForwardingRule(key, obj, m.Objects, meta.VersionAlpha, projectID)
}
// AddressAttributes maps from Address key to a map of Instances
type AddressAttributes struct {
IPCounter int // Used to assign Addresses with no IP a unique IP address
}
func convertAndInsertAlphaAddress(key *meta.Key, obj gceObject, mAddrs map[meta.Key]*cloud.MockAddressesObj, version meta.Version, projectID string, addressAttrs AddressAttributes) (bool, error) {
if !key.Valid() {
return true, fmt.Errorf("invalid GCE key (%+v)", key)
}
if _, ok := mAddrs[*key]; ok {
err := &googleapi.Error{
Code: http.StatusConflict,
Message: fmt.Sprintf("MockAddresses %v exists", key),
}
return true, err
}
enc, err := obj.MarshalJSON()
if err != nil {
return true, err
}
var addr alpha.Address
if err := json.Unmarshal(enc, &addr); err != nil {
return true, err
}
// Set default address type if not present.
if addr.AddressType == "" {
addr.AddressType = string(cloud.SchemeExternal)
}
var existingAddresses []*ga.Address
for _, obj := range mAddrs {
existingAddresses = append(existingAddresses, obj.ToGA())
}
for _, existingAddr := range existingAddresses {
if addr.Address == existingAddr.Address {
msg := fmt.Sprintf("MockAddresses IP %v in use", addr.Address)
// When the IP is already in use, this call returns a StatusBadRequest
// if the address is an external address, and StatusConflict if an
// internal address. This is to be consistent with actual GCE API.
errorCode := http.StatusConflict
if addr.AddressType == string(cloud.SchemeExternal) {
errorCode = http.StatusBadRequest
}
return true, &googleapi.Error{Code: errorCode, Message: msg}
}
}
// Set default values used in tests
addr.Name = key.Name
if addr.SelfLink == "" {
addr.SelfLink = cloud.SelfLink(version, projectID, "addresses", key)
}
if addr.Address == "" {
addr.Address = fmt.Sprintf("1.2.3.%d", addressAttrs.IPCounter)
addressAttrs.IPCounter++
}
// Set the default values for the Alpha fields.
if addr.NetworkTier == "" {
addr.NetworkTier = cloud.NetworkTierDefault.ToGCEValue()
}
mAddrs[*key] = &cloud.MockAddressesObj{Obj: addr}
return true, nil
}
// InsertAddressHook mocks inserting an Address.
func InsertAddressHook(ctx context.Context, key *meta.Key, obj *ga.Address, m *cloud.MockAddresses) (bool, error) {
m.Lock.Lock()
defer m.Lock.Unlock()
projectID := m.ProjectRouter.ProjectID(ctx, meta.VersionGA, "addresses")
return convertAndInsertAlphaAddress(key, obj, m.Objects, meta.VersionGA, projectID, m.X.(AddressAttributes))
}
// InsertBetaAddressHook mocks inserting a BetaAddress.
func InsertBetaAddressHook(ctx context.Context, key *meta.Key, obj *beta.Address, m *cloud.MockAddresses) (bool, error) {
m.Lock.Lock()
defer m.Lock.Unlock()
projectID := m.ProjectRouter.ProjectID(ctx, meta.VersionBeta, "addresses")
return convertAndInsertAlphaAddress(key, obj, m.Objects, meta.VersionBeta, projectID, m.X.(AddressAttributes))
}
// InsertAlphaAddressHook mocks inserting an Address. Addresses are expected to
// default to Premium tier if no NetworkTier is specified.
func InsertAlphaAddressHook(ctx context.Context, key *meta.Key, obj *alpha.Address, m *cloud.MockAlphaAddresses) (bool, error) {
m.Lock.Lock()
defer m.Lock.Unlock()
projectID := m.ProjectRouter.ProjectID(ctx, meta.VersionBeta, "addresses")
return convertAndInsertAlphaAddress(key, obj, m.Objects, meta.VersionAlpha, projectID, m.X.(AddressAttributes))
}
// InstanceGroupAttributes maps from InstanceGroup key to a map of Instances
type InstanceGroupAttributes struct {
InstanceMap map[meta.Key]map[string]*ga.InstanceWithNamedPorts
Lock *sync.Mutex
}
// AddInstances adds a list of Instances passed by InstanceReference
func (igAttrs *InstanceGroupAttributes) AddInstances(key *meta.Key, instanceRefs []*ga.InstanceReference) error {
igAttrs.Lock.Lock()
defer igAttrs.Lock.Unlock()
instancesWithNamedPorts, ok := igAttrs.InstanceMap[*key]
if !ok {
instancesWithNamedPorts = make(map[string]*ga.InstanceWithNamedPorts)
}
for _, instance := range instanceRefs {
iWithPort := &ga.InstanceWithNamedPorts{
Instance: instance.Instance,
}
instancesWithNamedPorts[instance.Instance] = iWithPort
}
igAttrs.InstanceMap[*key] = instancesWithNamedPorts
return nil
}
// RemoveInstances removes a list of Instances passed by InstanceReference
func (igAttrs *InstanceGroupAttributes) RemoveInstances(key *meta.Key, instanceRefs []*ga.InstanceReference) error {
igAttrs.Lock.Lock()
defer igAttrs.Lock.Unlock()
instancesWithNamedPorts, ok := igAttrs.InstanceMap[*key]
if !ok {
instancesWithNamedPorts = make(map[string]*ga.InstanceWithNamedPorts)
}
for _, instanceToRemove := range instanceRefs {
if _, ok := instancesWithNamedPorts[instanceToRemove.Instance]; ok {
delete(instancesWithNamedPorts, instanceToRemove.Instance)
} else {
return &googleapi.Error{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("%s is not a member of %s", instanceToRemove.Instance, key.String()),
}
}
}
igAttrs.InstanceMap[*key] = instancesWithNamedPorts
return nil
}
// List gets a list of InstanceWithNamedPorts
func (igAttrs *InstanceGroupAttributes) List(key *meta.Key) []*ga.InstanceWithNamedPorts {
igAttrs.Lock.Lock()
defer igAttrs.Lock.Unlock()
instancesWithNamedPorts, ok := igAttrs.InstanceMap[*key]
if !ok {
instancesWithNamedPorts = make(map[string]*ga.InstanceWithNamedPorts)
}
var instanceList []*ga.InstanceWithNamedPorts
for _, val := range instancesWithNamedPorts {
instanceList = append(instanceList, val)
}
return instanceList
}
// AddInstancesHook mocks adding instances from an InstanceGroup
func AddInstancesHook(ctx context.Context, key *meta.Key, req *ga.InstanceGroupsAddInstancesRequest, m *cloud.MockInstanceGroups) error {
_, err := m.Get(ctx, key)
if err != nil {
return &googleapi.Error{
Code: http.StatusNotFound,
Message: fmt.Sprintf("Key: %s was not found in InstanceGroups", key.String()),
}
}
var attrs InstanceGroupAttributes
attrs = m.X.(InstanceGroupAttributes)
attrs.AddInstances(key, req.Instances)
m.X = attrs
return nil
}
// ListInstancesHook mocks listing instances from an InstanceGroup
func ListInstancesHook(ctx context.Context, key *meta.Key, req *ga.InstanceGroupsListInstancesRequest, filter *filter.F, m *cloud.MockInstanceGroups) ([]*ga.InstanceWithNamedPorts, error) {
_, err := m.Get(ctx, key)
if err != nil {
return nil, &googleapi.Error{
Code: http.StatusNotFound,
Message: fmt.Sprintf("Key: %s was not found in InstanceGroups", key.String()),
}
}
var attrs InstanceGroupAttributes
attrs = m.X.(InstanceGroupAttributes)
instances := attrs.List(key)
return instances, nil
}
// RemoveInstancesHook mocks removing instances from an InstanceGroup
func RemoveInstancesHook(ctx context.Context, key *meta.Key, req *ga.InstanceGroupsRemoveInstancesRequest, m *cloud.MockInstanceGroups) error {
_, err := m.Get(ctx, key)
if err != nil {
return &googleapi.Error{
Code: http.StatusNotFound,
Message: fmt.Sprintf("Key: %s was not found in InstanceGroups", key.String()),
}
}
var attrs InstanceGroupAttributes
attrs = m.X.(InstanceGroupAttributes)
attrs.RemoveInstances(key, req.Instances)
m.X = attrs
return nil
}
// UpdateFirewallHook defines the hook for updating a Firewall. It replaces the
// object with the same key in the mock with the updated object.
func UpdateFirewallHook(ctx context.Context, key *meta.Key, obj *ga.Firewall, m *cloud.MockFirewalls) error {
_, err := m.Get(ctx, key)
if err != nil {
return &googleapi.Error{
Code: http.StatusNotFound,
Message: fmt.Sprintf("Key: %s was not found in Firewalls", key.String()),
}
}
obj.Name = key.Name
projectID := m.ProjectRouter.ProjectID(ctx, "ga", "firewalls")
obj.SelfLink = cloud.SelfLink(meta.VersionGA, projectID, "firewalls", key)
m.Objects[*key] = &cloud.MockFirewallsObj{Obj: obj}
return nil
}
// UpdateHealthCheckHook defines the hook for updating a HealthCheck. It
// replaces the object with the same key in the mock with the updated object.
func UpdateHealthCheckHook(ctx context.Context, key *meta.Key, obj *ga.HealthCheck, m *cloud.MockHealthChecks) error {
_, err := m.Get(ctx, key)
if err != nil {
return &googleapi.Error{
Code: http.StatusNotFound,
Message: fmt.Sprintf("Key: %s was not found in HealthChecks", key.String()),
}
}
obj.Name = key.Name
projectID := m.ProjectRouter.ProjectID(ctx, "ga", "healthChecks")
obj.SelfLink = cloud.SelfLink(meta.VersionGA, projectID, "healthChecks", key)
m.Objects[*key] = &cloud.MockHealthChecksObj{Obj: obj}
return nil
}
// UpdateRegionBackendServiceHook defines the hook for updating a Region
// BackendsService. It replaces the object with the same key in the mock with
// the updated object.
func UpdateRegionBackendServiceHook(ctx context.Context, key *meta.Key, obj *ga.BackendService, m *cloud.MockRegionBackendServices) error {
_, err := m.Get(ctx, key)
if err != nil {
return &googleapi.Error{
Code: http.StatusNotFound,
Message: fmt.Sprintf("Key: %s was not found in RegionBackendServices", key.String()),
}
}
obj.Name = key.Name
projectID := m.ProjectRouter.ProjectID(ctx, "ga", "backendServices")
obj.SelfLink = cloud.SelfLink(meta.VersionGA, projectID, "backendServices", key)
m.Objects[*key] = &cloud.MockRegionBackendServicesObj{Obj: obj}
return nil
}
// UpdateBackendServiceHook defines the hook for updating a BackendService.
// It replaces the object with the same key in the mock with the updated object.
func UpdateBackendServiceHook(ctx context.Context, key *meta.Key, obj *ga.BackendService, m *cloud.MockBackendServices) error {
_, err := m.Get(ctx, key)
if err != nil {
return &googleapi.Error{
Code: http.StatusNotFound,
Message: fmt.Sprintf("Key: %s was not found in BackendServices", key.String()),
}
}
obj.Name = key.Name
projectID := m.ProjectRouter.ProjectID(ctx, "ga", "backendServices")
obj.SelfLink = cloud.SelfLink(meta.VersionGA, projectID, "backendServices", key)
m.Objects[*key] = &cloud.MockBackendServicesObj{Obj: obj}
return nil
}
// UpdateAlphaBackendServiceHook defines the hook for updating an alpha BackendService.
// It replaces the object with the same key in the mock with the updated object.
func UpdateAlphaBackendServiceHook(ctx context.Context, key *meta.Key, obj *alpha.BackendService, m *cloud.MockAlphaBackendServices) error {
_, err := m.Get(ctx, key)
if err != nil {
return &googleapi.Error{
Code: http.StatusNotFound,
Message: fmt.Sprintf("Key: %s was not found in BackendServices", key.String()),
}
}
obj.Name = key.Name
projectID := m.ProjectRouter.ProjectID(ctx, "alpha", "backendServices")
obj.SelfLink = cloud.SelfLink(meta.VersionAlpha, projectID, "backendServices", key)
m.Objects[*key] = &cloud.MockBackendServicesObj{Obj: obj}
return nil
}
// UpdateBetaBackendServiceHook defines the hook for updating an beta BackendService.
// It replaces the object with the same key in the mock with the updated object.
func UpdateBetaBackendServiceHook(ctx context.Context, key *meta.Key, obj *beta.BackendService, m *cloud.MockBetaBackendServices) error {
_, err := m.Get(ctx, key)
if err != nil {
return &googleapi.Error{
Code: http.StatusNotFound,
Message: fmt.Sprintf("Key: %s was not found in BackendServices", key.String()),
}
}
obj.Name = key.Name
projectID := m.ProjectRouter.ProjectID(ctx, "beta", "backendServices")
obj.SelfLink = cloud.SelfLink(meta.VersionBeta, projectID, "backendServices", key)
m.Objects[*key] = &cloud.MockBackendServicesObj{Obj: obj}
return nil
}
// UpdateURLMapHook defines the hook for updating a UrlMap.
// It replaces the object with the same key in the mock with the updated object.
func UpdateURLMapHook(ctx context.Context, key *meta.Key, obj *ga.UrlMap, m *cloud.MockUrlMaps) error {
_, err := m.Get(ctx, key)
if err != nil {
return &googleapi.Error{
Code: http.StatusNotFound,
Message: fmt.Sprintf("Key: %s was not found in UrlMaps", key.String()),
}
}
obj.Name = key.Name
projectID := m.ProjectRouter.ProjectID(ctx, "ga", "urlMaps")
obj.SelfLink = cloud.SelfLink(meta.VersionGA, projectID, "urlMaps", key)
m.Objects[*key] = &cloud.MockUrlMapsObj{Obj: obj}
return nil
}
// InsertFirewallsUnauthorizedErrHook mocks firewall insertion. A forbidden error will be thrown as return.
func InsertFirewallsUnauthorizedErrHook(ctx context.Context, key *meta.Key, obj *ga.Firewall, m *cloud.MockFirewalls) (bool, error) {
return true, &googleapi.Error{Code: http.StatusForbidden}
}
// UpdateFirewallsUnauthorizedErrHook mocks firewall updating. A forbidden error will be thrown as return.
func UpdateFirewallsUnauthorizedErrHook(ctx context.Context, key *meta.Key, obj *ga.Firewall, m *cloud.MockFirewalls) error {
return &googleapi.Error{Code: http.StatusForbidden}
}
// DeleteFirewallsUnauthorizedErrHook mocks firewall deletion. A forbidden error will be thrown as return.
func DeleteFirewallsUnauthorizedErrHook(ctx context.Context, key *meta.Key, m *cloud.MockFirewalls) (bool, error) {
return true, &googleapi.Error{Code: http.StatusForbidden}
}
// GetFirewallsUnauthorizedErrHook mocks firewall information retrival. A forbidden error will be thrown as return.
func GetFirewallsUnauthorizedErrHook(ctx context.Context, key *meta.Key, m *cloud.MockFirewalls) (bool, *ga.Firewall, error) {
return true, nil, &googleapi.Error{Code: http.StatusForbidden}
}
// GetTargetPoolInternalErrHook mocks getting target pool. It returns a internal server error.
func GetTargetPoolInternalErrHook(ctx context.Context, key *meta.Key, m *cloud.MockTargetPools) (bool, *ga.TargetPool, error) {
return true, nil, InternalServerError
}
// GetForwardingRulesInternalErrHook mocks getting forwarding rules and returns an internal server error.
func GetForwardingRulesInternalErrHook(ctx context.Context, key *meta.Key, m *cloud.MockForwardingRules) (bool, *ga.ForwardingRule, error) {
return true, nil, InternalServerError
}
// GetAddressesInternalErrHook mocks getting network address and returns an internal server error.
func GetAddressesInternalErrHook(ctx context.Context, key *meta.Key, m *cloud.MockAddresses) (bool, *ga.Address, error) {
return true, nil, InternalServerError
}
// GetHTTPHealthChecksInternalErrHook mocks getting http health check and returns an internal server error.
func GetHTTPHealthChecksInternalErrHook(ctx context.Context, key *meta.Key, m *cloud.MockHttpHealthChecks) (bool, *ga.HttpHealthCheck, error) {
return true, nil, InternalServerError
}
// InsertTargetPoolsInternalErrHook mocks getting target pool and returns an internal server error.
func InsertTargetPoolsInternalErrHook(ctx context.Context, key *meta.Key, obj *ga.TargetPool, m *cloud.MockTargetPools) (bool, error) {
return true, InternalServerError
}
// InsertForwardingRulesInternalErrHook mocks getting forwarding rule and returns an internal server error.
func InsertForwardingRulesInternalErrHook(ctx context.Context, key *meta.Key, obj *ga.ForwardingRule, m *cloud.MockForwardingRules) (bool, error) {
return true, InternalServerError
}
// DeleteAddressesNotFoundErrHook mocks deleting network address and returns a not found error.
func DeleteAddressesNotFoundErrHook(ctx context.Context, key *meta.Key, m *cloud.MockAddresses) (bool, error) {
return true, &googleapi.Error{Code: http.StatusNotFound}
}
// DeleteAddressesInternalErrHook mocks deleting address and returns an internal server error.
func DeleteAddressesInternalErrHook(ctx context.Context, key *meta.Key, m *cloud.MockAddresses) (bool, error) {
return true, InternalServerError
}
// InsertAlphaBackendServiceUnauthorizedErrHook mocks inserting an alpha BackendService and returns a forbidden error.
func InsertAlphaBackendServiceUnauthorizedErrHook(ctx context.Context, key *meta.Key, obj *alpha.BackendService, m *cloud.MockAlphaBackendServices) (bool, error) {
return true, UnauthorizedErr
}
// UpdateAlphaBackendServiceUnauthorizedErrHook mocks updating an alpha BackendService and returns a forbidden error.
func UpdateAlphaBackendServiceUnauthorizedErrHook(ctx context.Context, key *meta.Key, obj *alpha.BackendService, m *cloud.MockAlphaBackendServices) error {
return UnauthorizedErr
}
// GetRegionBackendServicesErrHook mocks getting region backend service and returns an internal server error.
func GetRegionBackendServicesErrHook(ctx context.Context, key *meta.Key, m *cloud.MockRegionBackendServices) (bool, *ga.BackendService, error) {
return true, nil, InternalServerError
}
// UpdateRegionBackendServicesErrHook mocks updating a reegion backend service and returns an internal server error.
func UpdateRegionBackendServicesErrHook(ctx context.Context, key *meta.Key, svc *ga.BackendService, m *cloud.MockRegionBackendServices) error {
return InternalServerError
}
// DeleteRegionBackendServicesErrHook mocks deleting region backend service and returns an internal server error.
func DeleteRegionBackendServicesErrHook(ctx context.Context, key *meta.Key, m *cloud.MockRegionBackendServices) (bool, error) {
return true, InternalServerError
}
// DeleteRegionBackendServicesInUseErrHook mocks deleting region backend service and returns an InUseError.
func DeleteRegionBackendServicesInUseErrHook(ctx context.Context, key *meta.Key, m *cloud.MockRegionBackendServices) (bool, error) {
return true, InUseError
}
// GetInstanceGroupInternalErrHook mocks getting instance group and returns an internal server error.
func GetInstanceGroupInternalErrHook(ctx context.Context, key *meta.Key, m *cloud.MockInstanceGroups) (bool, *ga.InstanceGroup, error) {
return true, nil, InternalServerError
}
// GetHealthChecksInternalErrHook mocks getting health check and returns an internal server erorr.
func GetHealthChecksInternalErrHook(ctx context.Context, key *meta.Key, m *cloud.MockHealthChecks) (bool, *ga.HealthCheck, error) {
return true, nil, InternalServerError
}
// DeleteHealthChecksInternalErrHook mocks deleting health check and returns an internal server error.
func DeleteHealthChecksInternalErrHook(ctx context.Context, key *meta.Key, m *cloud.MockHealthChecks) (bool, error) {
return true, InternalServerError
}
// DeleteHealthChecksInuseErrHook mocks deleting health check and returns an in use error.
func DeleteHealthChecksInuseErrHook(ctx context.Context, key *meta.Key, m *cloud.MockHealthChecks) (bool, error) {
return true, InUseError
}
// DeleteForwardingRuleErrHook mocks deleting forwarding rule and returns an internal server error.
func DeleteForwardingRuleErrHook(ctx context.Context, key *meta.Key, m *cloud.MockForwardingRules) (bool, error) {
return true, InternalServerError
}
// ListZonesInternalErrHook mocks listing zone and returns an internal server error.
func ListZonesInternalErrHook(ctx context.Context, fl *filter.F, m *cloud.MockZones) (bool, []*ga.Zone, error) {
return true, []*ga.Zone{}, InternalServerError
}
// DeleteInstanceGroupInternalErrHook mocks deleting instance group and returns an internal server error.
func DeleteInstanceGroupInternalErrHook(ctx context.Context, key *meta.Key, m *cloud.MockInstanceGroups) (bool, error) {
return true, InternalServerError
}

View File

@ -0,0 +1,219 @@
/*
Copyright 2018 Google LLC
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
https://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"
"k8s.io/klog"
alpha "google.golang.org/api/compute/v0.alpha"
beta "google.golang.org/api/compute/v0.beta"
ga "google.golang.org/api/compute/v1"
"google.golang.org/api/googleapi"
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"
)
const (
operationStatusDone = "DONE"
)
// 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)
// error returns the resulting error of the operation. This may be nil if the operations
// was successful.
error() 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
projectID string
key *meta.Key
err error
}
func (o *gaOperation) String() string {
return fmt.Sprintf("gaOperation{%q, %v}", o.projectID, o.key)
}
func (o *gaOperation) isDone(ctx context.Context) (bool, error) {
var (
op *ga.Operation
err error
)
switch o.key.Type() {
case meta.Regional:
op, err = o.s.GA.RegionOperations.Get(o.projectID, o.key.Region, o.key.Name).Context(ctx).Do()
klog.V(5).Infof("GA.RegionOperations.Get(%v, %v, %v) = %+v, %v; ctx = %v", o.projectID, o.key.Region, o.key.Name, op, err, ctx)
case meta.Zonal:
op, err = o.s.GA.ZoneOperations.Get(o.projectID, o.key.Zone, o.key.Name).Context(ctx).Do()
klog.V(5).Infof("GA.ZoneOperations.Get(%v, %v, %v) = %+v, %v; ctx = %v", o.projectID, o.key.Zone, o.key.Name, op, err, ctx)
case meta.Global:
op, err = o.s.GA.GlobalOperations.Get(o.projectID, o.key.Name).Context(ctx).Do()
klog.V(5).Infof("GA.GlobalOperations.Get(%v, %v) = %+v, %v; ctx = %v", o.projectID, o.key.Name, op, err, ctx)
default:
return false, fmt.Errorf("invalid key type: %#v", o.key)
}
if err != nil {
return false, err
}
if op == nil || op.Status != operationStatusDone {
return false, nil
}
if op.Error != nil && len(op.Error.Errors) > 0 && op.Error.Errors[0] != nil {
e := op.Error.Errors[0]
o.err = &googleapi.Error{Code: int(op.HttpErrorStatusCode), Message: fmt.Sprintf("%v - %v", e.Code, e.Message)}
}
return true, nil
}
func (o *gaOperation) rateLimitKey() *RateLimitKey {
return &RateLimitKey{
ProjectID: o.projectID,
Operation: "Get",
Service: "Operations",
Version: meta.VersionGA,
}
}
func (o *gaOperation) error() error {
return o.err
}
type alphaOperation struct {
s *Service
projectID string
key *meta.Key
err error
}
func (o *alphaOperation) String() string {
return fmt.Sprintf("alphaOperation{%q, %v}", o.projectID, o.key)
}
func (o *alphaOperation) isDone(ctx context.Context) (bool, error) {
var (
op *alpha.Operation
err error
)
switch o.key.Type() {
case meta.Regional:
op, err = o.s.Alpha.RegionOperations.Get(o.projectID, o.key.Region, o.key.Name).Context(ctx).Do()
klog.V(5).Infof("Alpha.RegionOperations.Get(%v, %v, %v) = %+v, %v; ctx = %v", o.projectID, o.key.Region, o.key.Name, op, err, ctx)
case meta.Zonal:
op, err = o.s.Alpha.ZoneOperations.Get(o.projectID, o.key.Zone, o.key.Name).Context(ctx).Do()
klog.V(5).Infof("Alpha.ZoneOperations.Get(%v, %v, %v) = %+v, %v; ctx = %v", o.projectID, o.key.Zone, o.key.Name, op, err, ctx)
case meta.Global:
op, err = o.s.Alpha.GlobalOperations.Get(o.projectID, o.key.Name).Context(ctx).Do()
klog.V(5).Infof("Alpha.GlobalOperations.Get(%v, %v) = %+v, %v; ctx = %v", o.projectID, o.key.Name, op, err, ctx)
default:
return false, fmt.Errorf("invalid key type: %#v", o.key)
}
if err != nil {
return false, err
}
if op == nil || op.Status != operationStatusDone {
return false, nil
}
if op.Error != nil && len(op.Error.Errors) > 0 && op.Error.Errors[0] != nil {
e := op.Error.Errors[0]
o.err = &googleapi.Error{Code: int(op.HttpErrorStatusCode), Message: fmt.Sprintf("%v - %v", e.Code, e.Message)}
}
return true, nil
}
func (o *alphaOperation) rateLimitKey() *RateLimitKey {
return &RateLimitKey{
ProjectID: o.projectID,
Operation: "Get",
Service: "Operations",
Version: meta.VersionAlpha,
}
}
func (o *alphaOperation) error() error {
return o.err
}
type betaOperation struct {
s *Service
projectID string
key *meta.Key
err error
}
func (o *betaOperation) String() string {
return fmt.Sprintf("betaOperation{%q, %v}", o.projectID, o.key)
}
func (o *betaOperation) isDone(ctx context.Context) (bool, error) {
var (
op *beta.Operation
err error
)
switch o.key.Type() {
case meta.Regional:
op, err = o.s.Beta.RegionOperations.Get(o.projectID, o.key.Region, o.key.Name).Context(ctx).Do()
klog.V(5).Infof("Beta.RegionOperations.Get(%v, %v, %v) = %+v, %v; ctx = %v", o.projectID, o.key.Region, o.key.Name, op, err, ctx)
case meta.Zonal:
op, err = o.s.Beta.ZoneOperations.Get(o.projectID, o.key.Zone, o.key.Name).Context(ctx).Do()
klog.V(5).Infof("Beta.ZoneOperations.Get(%v, %v, %v) = %+v, %v; ctx = %v", o.projectID, o.key.Zone, o.key.Name, op, err, ctx)
case meta.Global:
op, err = o.s.Beta.GlobalOperations.Get(o.projectID, o.key.Name).Context(ctx).Do()
klog.V(5).Infof("Beta.GlobalOperations.Get(%v, %v) = %+v, %v; ctx = %v", o.projectID, o.key.Name, op, err, ctx)
default:
return false, fmt.Errorf("invalid key type: %#v", o.key)
}
if err != nil {
return false, err
}
if op == nil || op.Status != operationStatusDone {
return false, nil
}
if op.Error != nil && len(op.Error.Errors) > 0 && op.Error.Errors[0] != nil {
e := op.Error.Errors[0]
o.err = &googleapi.Error{Code: int(op.HttpErrorStatusCode), Message: fmt.Sprintf("%v - %v", e.Code, e.Message)}
}
return true, nil
}
func (o *betaOperation) rateLimitKey() *RateLimitKey {
return &RateLimitKey{
ProjectID: o.projectID,
Operation: "Get",
Service: "Operations",
Version: meta.VersionBeta,
}
}
func (o *betaOperation) error() error {
return o.err
}

View File

@ -0,0 +1,45 @@
/*
Copyright 2018 Google LLC
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
https://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"
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/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
}

View File

@ -0,0 +1,106 @@
/*
Copyright 2018 Google LLC
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
https://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"
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/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
}
// acceptor is an object which blocks within Accept until a call is allowed to run.
// Accept is a behavior of the flowcontrol.RateLimiter interface.
type acceptor interface {
// Accept blocks until a call is allowed to run.
Accept()
}
// AcceptRateLimiter wraps an Acceptor with RateLimiter parameters.
type AcceptRateLimiter struct {
// Acceptor is the underlying rate limiter.
Acceptor acceptor
}
// Accept wraps an Acceptor and blocks on Accept or context.Done(). Key is ignored.
func (rl *AcceptRateLimiter) Accept(ctx context.Context, key *RateLimitKey) error {
ch := make(chan struct{})
go func() {
rl.Acceptor.Accept()
close(ch)
}()
select {
case <-ch:
break
case <-ctx.Done():
return ctx.Err()
}
return nil
}
// NopRateLimiter is a rate limiter that performs no rate limiting.
type NopRateLimiter struct {
}
// Accept everything immediately.
func (*NopRateLimiter) Accept(ctx context.Context, key *RateLimitKey) error {
return nil
}
// MinimumRateLimiter wraps a RateLimiter and will only call its Accept until the minimum
// duration has been met or the context is cancelled.
type MinimumRateLimiter struct {
// RateLimiter is the underlying ratelimiter which is called after the mininum time is reacehd.
RateLimiter RateLimiter
// Minimum is the minimum wait time before the underlying ratelimiter is called.
Minimum time.Duration
}
// Accept blocks on the minimum duration and context. Once the minimum duration is met,
// the func is blocked on the underlying ratelimiter.
func (m *MinimumRateLimiter) Accept(ctx context.Context, key *RateLimitKey) error {
select {
case <-time.After(m.Minimum):
return m.RateLimiter.Accept(ctx, key)
case <-ctx.Done():
return ctx.Err()
}
}

View File

@ -0,0 +1,110 @@
/*
Copyright 2018 Google LLC
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
https://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"
"k8s.io/klog"
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 (s *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{s: s, projectID: r.ProjectID, key: r.Key}, nil
case *alpha.Operation:
r, err := ParseResourceURL(o.SelfLink)
if err != nil {
return nil, err
}
return &alphaOperation{s: s, projectID: r.ProjectID, key: r.Key}, nil
case *beta.Operation:
r, err := ParseResourceURL(o.SelfLink)
if err != nil {
return nil, err
}
return &betaOperation{s: s, projectID: r.ProjectID, key: r.Key}, 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 (s *Service) WaitForCompletion(ctx context.Context, genericOp interface{}) error {
op, err := s.wrapOperation(genericOp)
if err != nil {
klog.Errorf("wrapOperation(%+v) error: %v", genericOp, err)
return err
}
return s.pollOperation(ctx, op)
}
// pollOperation calls operations.isDone until the function comes back true or context is Done.
// If an error occurs retrieving the operation, the loop will continue until the context is done.
// This is to prevent a transient error from bubbling up to controller-level logic.
func (s *Service) pollOperation(ctx context.Context, op operation) error {
var pollCount int
for {
// Check if context has been cancelled. Note that ctx.Done() must be checked before
// returning ctx.Err().
select {
case <-ctx.Done():
klog.V(5).Infof("op.pollOperation(%v, %v) not completed, poll count = %d, ctx.Err = %v", ctx, op, pollCount, ctx.Err())
return ctx.Err()
default:
// ctx is not canceled, continue immediately
}
pollCount++
klog.V(5).Infof("op.isDone(%v) waiting; op = %v, poll count = %d", ctx, op, pollCount)
s.RateLimiter.Accept(ctx, op.rateLimitKey())
done, err := op.isDone(ctx)
if err != nil {
klog.V(5).Infof("op.isDone(%v) error; op = %v, poll count = %d, err = %v, retrying", ctx, op, pollCount, err)
}
if done {
break
}
}
klog.V(5).Infof("op.isDone(%v) complete; op = %v, poll count = %d, op.err = %v", ctx, op, pollCount, op.error())
return op.error()
}

View File

@ -0,0 +1,201 @@
/*
Copyright 2018 Google LLC
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
https://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"
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"
)
const (
gaPrefix = "https://www.googleapis.com/compute/v1"
alphaPrefix = "https://www.googleapis.com/compute/alpha"
betaPrefix = "https://www.googleapis.com/compute/beta"
)
// 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
}
// RelativeResourceName returns the relative resource name string
// representing this ResourceID.
func (r *ResourceID) RelativeResourceName() string {
return RelativeResourceName(r.ProjectID, r.Resource, r.Key)
}
// ResourcePath returns the resource path representing this ResourceID.
func (r *ResourceID) ResourcePath() string {
return ResourcePath(r.Resource, r.Key)
}
func (r *ResourceID) SelfLink(ver meta.Version) string {
return SelfLink(ver, r.ProjectID, r.Resource, r.Key)
}
// ParseResourceURL parses resource URLs of the following formats:
//
// global/<res>/<name>
// regions/<region>/<res>/<name>
// zones/<zone>/<res>/<name>
// projects/<proj>
// 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)
// Trim prefix off URL leaving "projects/..."
projectsIndex := strings.Index(url, "/projects/")
if projectsIndex >= 0 {
url = url[projectsIndex+1:]
}
parts := strings.Split(url, "/")
if len(parts) < 2 || len(parts) > 6 {
return nil, errNotValid
}
ret := &ResourceID{}
scopedName := parts
if parts[0] == "projects" {
ret.Resource = "projects"
ret.ProjectID = parts[1]
scopedName = parts[2:]
if len(scopedName) == 0 {
return ret, nil
}
}
switch scopedName[0] {
case "global":
if len(scopedName) != 3 {
return nil, errNotValid
}
ret.Resource = scopedName[1]
ret.Key = meta.GlobalKey(scopedName[2])
return ret, nil
case "regions":
switch len(scopedName) {
case 2:
ret.Resource = "regions"
ret.Key = meta.GlobalKey(scopedName[1])
return ret, nil
case 4:
ret.Resource = scopedName[2]
ret.Key = meta.RegionalKey(scopedName[3], scopedName[1])
return ret, nil
default:
return nil, errNotValid
}
case "zones":
switch len(scopedName) {
case 2:
ret.Resource = "zones"
ret.Key = meta.GlobalKey(scopedName[1])
return ret, nil
case 4:
ret.Resource = scopedName[2]
ret.Key = meta.ZonalKey(scopedName[3], scopedName[1])
return ret, nil
default:
return nil, errNotValid
}
}
return nil, errNotValid
}
func copyViaJSON(dest, src interface{}) error {
bytes, err := json.Marshal(src)
if err != nil {
return err
}
return json.Unmarshal(bytes, dest)
}
// ResourcePath returns the path starting from the location.
// Example: regions/us-central1/subnetworks/my-subnet
func ResourcePath(resource string, key *meta.Key) string {
switch resource {
case "zones", "regions":
return fmt.Sprintf("%s/%s", resource, key.Name)
case "projects":
return "invalid-resource"
}
switch key.Type() {
case meta.Zonal:
return fmt.Sprintf("zones/%s/%s/%s", key.Zone, resource, key.Name)
case meta.Regional:
return fmt.Sprintf("regions/%s/%s/%s", key.Region, resource, key.Name)
case meta.Global:
return fmt.Sprintf("global/%s/%s", resource, key.Name)
}
return "invalid-key-type"
}
// RelativeResourceName returns the path starting from project.
// Example: projects/my-project/regions/us-central1/subnetworks/my-subnet
func RelativeResourceName(project, resource string, key *meta.Key) string {
switch resource {
case "projects":
return fmt.Sprintf("projects/%s", project)
default:
return fmt.Sprintf("projects/%s/%s", project, ResourcePath(resource, key))
}
}
// 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"
}
return fmt.Sprintf("%s/%s", prefix, RelativeResourceName(project, resource, key))
}