From 6894e3d32bc9990c82b0681aff280e0f9b844db3 Mon Sep 17 00:00:00 2001 From: Bowei Du Date: Thu, 4 Jan 2018 23:21:53 -0800 Subject: [PATCH] Support utilities --- .../providers/gce/cloud/utils.go | 167 +++++++++++++++ .../providers/gce/cloud/utils_test.go | 197 ++++++++++++++++++ 2 files changed, 364 insertions(+) create mode 100644 pkg/cloudprovider/providers/gce/cloud/utils.go create mode 100644 pkg/cloudprovider/providers/gce/cloud/utils_test.go diff --git a/pkg/cloudprovider/providers/gce/cloud/utils.go b/pkg/cloudprovider/providers/gce/cloud/utils.go new file mode 100644 index 00000000000..dd4a07cfd05 --- /dev/null +++ b/pkg/cloudprovider/providers/gce/cloud/utils.go @@ -0,0 +1,167 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cloud + +import ( + "encoding/json" + "fmt" + "strings" + + "k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/meta" +) + +const ( + gaPrefix = "https://www.googleapis.com/compute/v1/" + alphaPrefix = "https://www.googleapis.com/compute/alpha/" + betaPrefix = "https://www.googleapis.com/compute/beta/" +) + +var ( + allPrefixes = []string{gaPrefix, alphaPrefix, betaPrefix} +) + +// ResourceID identifies a GCE resource as parsed from compute resource URL. +type ResourceID struct { + ProjectID string + Resource string + Key *meta.Key +} + +// Equal returns true if two resource IDs are equal. +func (r *ResourceID) Equal(other *ResourceID) bool { + if r.ProjectID != other.ProjectID || r.Resource != other.Resource { + return false + } + if r.Key != nil && other.Key != nil { + return *r.Key == *other.Key + } + if r.Key == nil && other.Key == nil { + return true + } + return false +} + +// ParseResourceURL parses resource URLs of the following formats: +// +// projects//global// +// projects//regions/// +// projects//zones/// +// [https://www.googleapis.com/compute/]/projects//global// +// [https://www.googleapis.com/compute/]/projects//regions/// +// [https://www.googleapis.com/compute/]/projects//zones/// +func ParseResourceURL(url string) (*ResourceID, error) { + errNotValid := fmt.Errorf("%q is not a valid resource URL", url) + + // Remove the "https://..." prefix if present + for _, prefix := range allPrefixes { + if strings.HasPrefix(url, prefix) { + if len(url) < len(prefix) { + return nil, errNotValid + } + url = url[len(prefix):] + break + } + } + + parts := strings.Split(url, "/") + if len(parts) < 2 || parts[0] != "projects" { + return nil, errNotValid + } + + ret := &ResourceID{ProjectID: parts[1]} + if len(parts) == 2 { + ret.Resource = "projects" + return ret, nil + } + + if len(parts) < 4 { + return nil, errNotValid + } + + if len(parts) == 4 { + switch parts[2] { + case "regions": + ret.Resource = "regions" + ret.Key = meta.GlobalKey(parts[3]) + return ret, nil + case "zones": + ret.Resource = "zones" + ret.Key = meta.GlobalKey(parts[3]) + return ret, nil + default: + return nil, errNotValid + } + } + + switch parts[2] { + case "global": + if len(parts) != 5 { + return nil, errNotValid + } + ret.Resource = parts[3] + ret.Key = meta.GlobalKey(parts[4]) + return ret, nil + case "regions": + if len(parts) != 6 { + return nil, errNotValid + } + ret.Resource = parts[4] + ret.Key = meta.RegionalKey(parts[5], parts[3]) + return ret, nil + case "zones": + if len(parts) != 6 { + return nil, errNotValid + } + ret.Resource = parts[4] + ret.Key = meta.ZonalKey(parts[5], parts[3]) + return ret, nil + } + return nil, errNotValid +} + +func copyViaJSON(dest, src interface{}) error { + bytes, err := json.Marshal(src) + if err != nil { + return err + } + return json.Unmarshal(bytes, dest) +} + +// SelfLink returns the self link URL for the given object. +func SelfLink(ver meta.Version, project, resource string, key meta.Key) string { + var prefix string + switch ver { + case meta.VersionAlpha: + prefix = alphaPrefix + case meta.VersionBeta: + prefix = betaPrefix + case meta.VersionGA: + prefix = gaPrefix + default: + prefix = "invalid-prefix" + } + + switch key.Type() { + case meta.Zonal: + return fmt.Sprintf("%sprojects/%s/zones/%s/%s/%s", prefix, project, key.Zone, resource, key.Name) + case meta.Regional: + return fmt.Sprintf("%sprojects/%s/regions/%s/%s/%s", prefix, project, key.Region, resource, key.Name) + case meta.Global: + return fmt.Sprintf("%sprojects/%s/%s/%s", prefix, project, resource, key.Name) + } + return "invalid-self-link" +} diff --git a/pkg/cloudprovider/providers/gce/cloud/utils_test.go b/pkg/cloudprovider/providers/gce/cloud/utils_test.go new file mode 100644 index 00000000000..823c8e73c88 --- /dev/null +++ b/pkg/cloudprovider/providers/gce/cloud/utils_test.go @@ -0,0 +1,197 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cloud + +import ( + "errors" + "testing" + + "k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/meta" +) + +func TestParseResourceURL(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + in string + r *ResourceID + }{ + { + "https://www.googleapis.com/compute/v1/projects/some-gce-project", + &ResourceID{"some-gce-project", "projects", nil}, + }, + { + "https://www.googleapis.com/compute/v1/projects/some-gce-project/regions/us-central1", + &ResourceID{"some-gce-project", "regions", meta.GlobalKey("us-central1")}, + }, + { + "https://www.googleapis.com/compute/v1/projects/some-gce-project/zones/us-central1-b", + &ResourceID{"some-gce-project", "zones", meta.GlobalKey("us-central1-b")}, + }, + { + "https://www.googleapis.com/compute/v1/projects/some-gce-project/global/operations/operation-1513289952196-56054460af5a0-b1dae0c3-9bbf9dbf", + &ResourceID{"some-gce-project", "operations", meta.GlobalKey("operation-1513289952196-56054460af5a0-b1dae0c3-9bbf9dbf")}, + }, + { + "https://www.googleapis.com/compute/alpha/projects/some-gce-project/regions/us-central1/addresses/my-address", + &ResourceID{"some-gce-project", "addresses", meta.RegionalKey("my-address", "us-central1")}, + }, + { + "https://www.googleapis.com/compute/v1/projects/some-gce-project/zones/us-central1-c/instances/instance-1", + &ResourceID{"some-gce-project", "instances", meta.ZonalKey("instance-1", "us-central1-c")}, + }, + { + "projects/some-gce-project", + &ResourceID{"some-gce-project", "projects", nil}, + }, + { + "projects/some-gce-project/regions/us-central1", + &ResourceID{"some-gce-project", "regions", meta.GlobalKey("us-central1")}, + }, + { + "projects/some-gce-project/zones/us-central1-b", + &ResourceID{"some-gce-project", "zones", meta.GlobalKey("us-central1-b")}, + }, + { + "projects/some-gce-project/global/operations/operation-1513289952196-56054460af5a0-b1dae0c3-9bbf9dbf", + &ResourceID{"some-gce-project", "operations", meta.GlobalKey("operation-1513289952196-56054460af5a0-b1dae0c3-9bbf9dbf")}, + }, + { + "projects/some-gce-project/regions/us-central1/addresses/my-address", + &ResourceID{"some-gce-project", "addresses", meta.RegionalKey("my-address", "us-central1")}, + }, + { + "projects/some-gce-project/zones/us-central1-c/instances/instance-1", + &ResourceID{"some-gce-project", "instances", meta.ZonalKey("instance-1", "us-central1-c")}, + }, + } { + r, err := ParseResourceURL(tc.in) + if err != nil { + t.Errorf("ParseResourceURL(%q) = %+v, %v; want _, nil", tc.in, r, err) + continue + } + if !r.Equal(tc.r) { + t.Errorf("ParseResourceURL(%q) = %+v, nil; want %+v, nil", tc.in, r, tc.r) + } + } + // Malformed URLs. + for _, tc := range []string{ + "", + "/", + "/a", + "/a/b", + "/a/b/c", + "/a/b/c/d", + "/a/b/c/d/e", + "/a/b/c/d/e/f", + "https://www.googleapis.com/compute/v1/projects/some-gce-project/global", + "projects/some-gce-project/global", + "projects/some-gce-project/global/foo/bar/baz", + "projects/some-gce-project/zones/us-central1-c/res", + "projects/some-gce-project/zones/us-central1-c/res/name/extra", + "https://www.googleapis.com/compute/gamma/projects/some-gce-project/global/addresses/name", + } { + r, err := ParseResourceURL(tc) + if err == nil { + t.Errorf("ParseResourceURL(%q) = %+v, %v, want _, error", tc, r, err) + } + } +} + +type A struct { + A, B, C string +} + +type B struct { + A, B, D string +} + +type E struct{} + +func (*E) MarshalJSON() ([]byte, error) { + return nil, errors.New("injected error") +} + +func TestCopyVisJSON(t *testing.T) { + t.Parallel() + + var b B + srcA := &A{"aa", "bb", "cc"} + err := copyViaJSON(&b, srcA) + if err != nil { + t.Errorf(`copyViaJSON(&b, %+v) = %v, want nil`, srcA, err) + } else { + expectedB := B{"aa", "bb", ""} + if b != expectedB { + t.Errorf("b == %+v, want %+v", b, expectedB) + } + } + + var a A + srcB := &B{"aaa", "bbb", "ccc"} + err = copyViaJSON(&a, srcB) + if err != nil { + t.Errorf(`copyViaJSON(&a, %+v) = %v, want nil`, srcB, err) + } else { + expectedA := A{"aaa", "bbb", ""} + if a != expectedA { + t.Errorf("a == %+v, want %+v", a, expectedA) + } + } + + if err := copyViaJSON(&a, &E{}); err == nil { + t.Errorf("copyViaJSON(&a, &E{}) = nil, want error") + } +} + +func TestSelfLink(t *testing.T) { + t.Parallel() + + for _, tc := range []struct{ + ver meta.Version + project string + resource string + key meta.Key + want string + }{ + { + meta.VersionAlpha, + "proj1", + "addresses", + *meta.RegionalKey("key1", "us-central1"), + "https://www.googleapis.com/compute/alpha/projects/proj1/regions/us-central1/addresses/key1", + }, + { + meta.VersionBeta, + "proj3", + "disks", + *meta.ZonalKey("key2", "us-central1-b"), + "https://www.googleapis.com/compute/beta/projects/proj3/zones/us-central1-b/disks/key2", + }, + { + meta.VersionGA, + "proj4", + "urlMaps", + *meta.GlobalKey("key3"), + "https://www.googleapis.com/compute/v1/projects/proj4/urlMaps/key3", + }, + }{ + if link := SelfLink(tc.ver, tc.project, tc.resource, tc.key); link != tc.want { + t.Errorf("SelfLink(%v, %q, %q, %v) = %v, want %q", tc.ver, tc.project, tc.resource, tc.key, link, tc.want) + } + } +}