From 1c6a978f00cb63a1d0630c0dad2299e52471fc31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Martins?= Date: Thu, 16 Feb 2017 06:55:25 +0000 Subject: [PATCH 01/23] kubelet/envvars: Adding brackets to IPv6 addresses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: André Martins --- pkg/kubelet/envvars/envvars.go | 8 ++++++-- pkg/kubelet/envvars/envvars_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/pkg/kubelet/envvars/envvars.go b/pkg/kubelet/envvars/envvars.go index 0adceb5be41..b86b444e8e8 100644 --- a/pkg/kubelet/envvars/envvars.go +++ b/pkg/kubelet/envvars/envvars.go @@ -18,6 +18,7 @@ package envvars import ( "fmt" + "net" "strconv" "strings" @@ -78,18 +79,21 @@ func makeLinkVariables(service *v1.Service) []v1.EnvVar { if sp.Protocol != "" { protocol = string(sp.Protocol) } + + hostPort := net.JoinHostPort(service.Spec.ClusterIP, strconv.Itoa(int(sp.Port))) + if i == 0 { // Docker special-cases the first port. all = append(all, v1.EnvVar{ Name: prefix + "_PORT", - Value: fmt.Sprintf("%s://%s:%d", strings.ToLower(protocol), service.Spec.ClusterIP, sp.Port), + Value: fmt.Sprintf("%s://%s", strings.ToLower(protocol), hostPort), }) } portPrefix := fmt.Sprintf("%s_PORT_%d_%s", prefix, sp.Port, strings.ToUpper(protocol)) all = append(all, []v1.EnvVar{ { Name: portPrefix, - Value: fmt.Sprintf("%s://%s:%d", strings.ToLower(protocol), service.Spec.ClusterIP, sp.Port), + Value: fmt.Sprintf("%s://%s", strings.ToLower(protocol), hostPort), }, { Name: portPrefix + "_PROTO", diff --git a/pkg/kubelet/envvars/envvars_test.go b/pkg/kubelet/envvars/envvars_test.go index 8b192526e2b..a4ce04340ec 100644 --- a/pkg/kubelet/envvars/envvars_test.go +++ b/pkg/kubelet/envvars/envvars_test.go @@ -79,6 +79,17 @@ func TestFromServices(t *testing.T) { }, }, }, + { + ObjectMeta: metav1.ObjectMeta{Name: "super-ipv6"}, + Spec: v1.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + ClusterIP: "2001:DB8::", + Ports: []v1.ServicePort{ + {Name: "u-d-p", Port: 8084, Protocol: "UDP"}, + {Name: "t-c-p", Port: 8084, Protocol: "TCP"}, + }, + }, + }, } vars := envvars.FromServices(sl) expected := []v1.EnvVar{ @@ -114,6 +125,19 @@ func TestFromServices(t *testing.T) { {Name: "Q_U_U_X_PORT_8083_TCP_PROTO", Value: "tcp"}, {Name: "Q_U_U_X_PORT_8083_TCP_PORT", Value: "8083"}, {Name: "Q_U_U_X_PORT_8083_TCP_ADDR", Value: "9.8.7.6"}, + {Name: "SUPER_IPV6_SERVICE_HOST", Value: "2001:DB8::"}, + {Name: "SUPER_IPV6_SERVICE_PORT", Value: "8084"}, + {Name: "SUPER_IPV6_SERVICE_PORT_U_D_P", Value: "8084"}, + {Name: "SUPER_IPV6_SERVICE_PORT_T_C_P", Value: "8084"}, + {Name: "SUPER_IPV6_PORT", Value: "udp://[2001:DB8::]:8084"}, + {Name: "SUPER_IPV6_PORT_8084_UDP", Value: "udp://[2001:DB8::]:8084"}, + {Name: "SUPER_IPV6_PORT_8084_UDP_PROTO", Value: "udp"}, + {Name: "SUPER_IPV6_PORT_8084_UDP_PORT", Value: "8084"}, + {Name: "SUPER_IPV6_PORT_8084_UDP_ADDR", Value: "2001:DB8::"}, + {Name: "SUPER_IPV6_PORT_8084_TCP", Value: "tcp://[2001:DB8::]:8084"}, + {Name: "SUPER_IPV6_PORT_8084_TCP_PROTO", Value: "tcp"}, + {Name: "SUPER_IPV6_PORT_8084_TCP_PORT", Value: "8084"}, + {Name: "SUPER_IPV6_PORT_8084_TCP_ADDR", Value: "2001:DB8::"}, } if len(vars) != len(expected) { t.Errorf("Expected %d env vars, got: %+v", len(expected), vars) From f9eafea8bf34bd7054342583cbb3d3a2fa55f907 Mon Sep 17 00:00:00 2001 From: Pengfei Ni Date: Fri, 12 May 2017 15:12:04 +0800 Subject: [PATCH 02/23] Add node e2e tests for hostNetwork --- test/e2e_node/security_context_test.go | 79 ++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/test/e2e_node/security_context_test.go b/test/e2e_node/security_context_test.go index f45b97dbb12..ff600806c54 100644 --- a/test/e2e_node/security_context_test.go +++ b/test/e2e_node/security_context_test.go @@ -18,6 +18,7 @@ package e2e_node import ( "fmt" + "net" "os/exec" "strings" @@ -195,4 +196,82 @@ var _ = framework.KubeDescribe("Security Context", func() { } }) }) + + Context("when creating a pod in the host network namespace", func() { + makeHostNetworkPod := func(podName, image string, command []string, hostNetwork bool) *v1.Pod { + return &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + }, + Spec: v1.PodSpec{ + RestartPolicy: v1.RestartPolicyNever, + HostNetwork: hostNetwork, + Containers: []v1.Container{ + { + Image: image, + Name: podName, + Command: command, + }, + }, + }, + } + } + listListeningPortsCommand := []string{"sh", "-c", "netstat -ln"} + createAndWaitHostNetworkPod := func(podName string, hostNetwork bool) { + podClient.Create(makeHostNetworkPod(podName, + "gcr.io/google_containers/busybox:1.24", + listListeningPortsCommand, + hostNetwork, + )) + + podClient.WaitForSuccess(podName, framework.PodStartTimeout) + } + + listeningPort := "" + var l net.Listener + BeforeEach(func() { + l, err := net.Listen("tcp", ":0") + if err != nil { + framework.Failf("Failed to open a new tcp port: %v", err) + } + addr := strings.Split(l.Addr().String(), ":") + listeningPort = addr[len(addr)-1] + framework.Logf("Opened a new tcp port %q", listeningPort) + }) + + It("should listen on same port in the host network containers", func() { + busyboxPodName := "busybox-hostnetwork-" + string(uuid.NewUUID()) + createAndWaitHostNetworkPod(busyboxPodName, true) + logs, err := framework.GetPodLogs(f.ClientSet, f.Namespace.Name, busyboxPodName, busyboxPodName) + if err != nil { + framework.Failf("GetPodLogs for pod %q failed: %v", busyboxPodName, err) + } + + framework.Logf("Got logs for pod %q: %q", busyboxPodName, logs) + if !strings.Contains(logs, listeningPort) { + framework.Failf("host-networked container should listening on same port as host") + } + }) + + It("shouldn't show the same port in the non-hostnetwork containers", func() { + busyboxPodName := "busybox-non-hostnetwork-" + string(uuid.NewUUID()) + createAndWaitHostNetworkPod(busyboxPodName, false) + logs, err := framework.GetPodLogs(f.ClientSet, f.Namespace.Name, busyboxPodName, busyboxPodName) + if err != nil { + framework.Failf("GetPodLogs for pod %q failed: %v", busyboxPodName, err) + } + + framework.Logf("Got logs for pod %q: %q", busyboxPodName, logs) + if strings.Contains(logs, listeningPort) { + framework.Failf("non-hostnetworked container shouldn't show the same port as host") + } + }) + + AfterEach(func() { + if l != nil { + l.Close() + } + }) + }) + }) From 9288025c28225af383556c9d671032afcf38c1fe Mon Sep 17 00:00:00 2001 From: Chao Xu Date: Fri, 5 May 2017 13:21:37 -0700 Subject: [PATCH 03/23] let client-gen understand vendor --- .../client-gen/generators/client_generator.go | 4 ++- .../fake/generator_fake_for_type.go | 3 +- .../generators/generator_for_group.go | 5 +-- .../generators/scheme/generator_for_scheme.go | 5 +-- cmd/libs/go2idl/client-gen/path/path.go | 31 +++++++++++++++++++ 5 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 cmd/libs/go2idl/client-gen/path/path.go diff --git a/cmd/libs/go2idl/client-gen/generators/client_generator.go b/cmd/libs/go2idl/client-gen/generators/client_generator.go index cafb98ee5fe..c2604d917e7 100644 --- a/cmd/libs/go2idl/client-gen/generators/client_generator.go +++ b/cmd/libs/go2idl/client-gen/generators/client_generator.go @@ -29,6 +29,7 @@ import ( clientgenargs "k8s.io/kubernetes/cmd/libs/go2idl/client-gen/args" "k8s.io/kubernetes/cmd/libs/go2idl/client-gen/generators/fake" "k8s.io/kubernetes/cmd/libs/go2idl/client-gen/generators/scheme" + "k8s.io/kubernetes/cmd/libs/go2idl/client-gen/path" clientgentypes "k8s.io/kubernetes/cmd/libs/go2idl/client-gen/types" "github.com/golang/glog" @@ -221,7 +222,8 @@ func Packages(context *generator.Context, arguments *args.GeneratorArgs) generat gvToTypes := map[clientgentypes.GroupVersion][]*types.Type{} for gv, inputDir := range customArgs.GroupVersionToInputPath { - p := context.Universe.Package(inputDir) + // Package are indexed with the vendor prefix stripped + p := context.Universe.Package(path.Vendorless(inputDir)) for n, t := range p.Types { // filter out types which are not included in user specified overrides. typesOverride, ok := includedTypesOverrides[gv] diff --git a/cmd/libs/go2idl/client-gen/generators/fake/generator_fake_for_type.go b/cmd/libs/go2idl/client-gen/generators/fake/generator_fake_for_type.go index c5c58719033..f6a4c3310b5 100644 --- a/cmd/libs/go2idl/client-gen/generators/fake/generator_fake_for_type.go +++ b/cmd/libs/go2idl/client-gen/generators/fake/generator_fake_for_type.go @@ -24,6 +24,7 @@ import ( "k8s.io/gengo/generator" "k8s.io/gengo/namer" "k8s.io/gengo/types" + "k8s.io/kubernetes/cmd/libs/go2idl/client-gen/path" ) // genFakeForType produces a file for each top-level type. @@ -99,7 +100,7 @@ func (g *genFakeForType) GenerateType(c *generator.Context, t *types.Type, w io. } // allow user to define a group name that's different from the one parsed from the directory. - p := c.Universe.Package(g.inputPackage) + p := c.Universe.Package(path.Vendorless(g.inputPackage)) if override := types.ExtractCommentTags("+", p.DocComments)["groupName"]; override != nil { groupName = override[0] } diff --git a/cmd/libs/go2idl/client-gen/generators/generator_for_group.go b/cmd/libs/go2idl/client-gen/generators/generator_for_group.go index 39de23e52f6..c419ad25149 100644 --- a/cmd/libs/go2idl/client-gen/generators/generator_for_group.go +++ b/cmd/libs/go2idl/client-gen/generators/generator_for_group.go @@ -23,6 +23,7 @@ import ( "k8s.io/gengo/generator" "k8s.io/gengo/namer" "k8s.io/gengo/types" + "k8s.io/kubernetes/cmd/libs/go2idl/client-gen/path" ) // genGroup produces a file for a group client, e.g. ExtensionsClient for the extension group. @@ -76,7 +77,7 @@ func (g *genGroup) GenerateType(c *generator.Context, t *types.Type, w io.Writer groupName = "" } // allow user to define a group name that's different from the one parsed from the directory. - p := c.Universe.Package(g.inputPackage) + p := c.Universe.Package(path.Vendorless(g.inputPackage)) if override := types.ExtractCommentTags("+", p.DocComments)["groupName"]; override != nil { groupName = override[0] } @@ -95,7 +96,7 @@ func (g *genGroup) GenerateType(c *generator.Context, t *types.Type, w io.Writer "restDefaultKubernetesUserAgent": c.Universe.Function(types.Name{Package: "k8s.io/client-go/rest", Name: "DefaultKubernetesUserAgent"}), "restRESTClientInterface": c.Universe.Type(types.Name{Package: "k8s.io/client-go/rest", Name: "Interface"}), "restRESTClientFor": c.Universe.Function(types.Name{Package: "k8s.io/client-go/rest", Name: "RESTClientFor"}), - "SchemeGroupVersion": c.Universe.Variable(types.Name{Package: g.inputPackage, Name: "SchemeGroupVersion"}), + "SchemeGroupVersion": c.Universe.Variable(types.Name{Package: path.Vendorless(g.inputPackage), Name: "SchemeGroupVersion"}), } sw.Do(groupInterfaceTemplate, m) sw.Do(groupClientTemplate, m) diff --git a/cmd/libs/go2idl/client-gen/generators/scheme/generator_for_scheme.go b/cmd/libs/go2idl/client-gen/generators/scheme/generator_for_scheme.go index 869c76a3b54..e4a2b02e14d 100644 --- a/cmd/libs/go2idl/client-gen/generators/scheme/generator_for_scheme.go +++ b/cmd/libs/go2idl/client-gen/generators/scheme/generator_for_scheme.go @@ -26,6 +26,7 @@ import ( "k8s.io/gengo/generator" "k8s.io/gengo/namer" "k8s.io/gengo/types" + "k8s.io/kubernetes/cmd/libs/go2idl/client-gen/path" clientgentypes "k8s.io/kubernetes/cmd/libs/go2idl/client-gen/types" ) @@ -66,10 +67,10 @@ func (g *GenScheme) Imports(c *generator.Context) (imports []string) { packagePath = filepath.Dir(packagePath) } packagePath = filepath.Join(packagePath, "install") - imports = append(imports, strings.ToLower(fmt.Sprintf("%s \"%s\"", group.Group.NonEmpty(), packagePath))) + imports = append(imports, strings.ToLower(fmt.Sprintf("%s \"%s\"", group.Group.NonEmpty(), path.Vendorless(packagePath)))) break } else { - imports = append(imports, strings.ToLower(fmt.Sprintf("%s%s \"%s\"", group.Group.NonEmpty(), version.NonEmpty(), packagePath))) + imports = append(imports, strings.ToLower(fmt.Sprintf("%s%s \"%s\"", group.Group.NonEmpty(), version.NonEmpty(), path.Vendorless(packagePath)))) } } } diff --git a/cmd/libs/go2idl/client-gen/path/path.go b/cmd/libs/go2idl/client-gen/path/path.go new file mode 100644 index 00000000000..19b269bdf28 --- /dev/null +++ b/cmd/libs/go2idl/client-gen/path/path.go @@ -0,0 +1,31 @@ +/* +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 path + +import "strings" + +// Vendorless removes the longest match of "*/vendor/" from the front of p. +// It is useful if a package locates in vendor/, e.g., +// k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/apis/meta/v1, because gengo +// indexes the package with its import path, e.g., +// k8s.io/apimachinery/pkg/apis/meta/v1, +func Vendorless(p string) string { + if pos := strings.LastIndex(p, "/vendor/"); pos != -1 { + return p[pos+len("/vendor/"):] + } + return p +} From bd6a9604dc2b41854c60204d1205820b23a03220 Mon Sep 17 00:00:00 2001 From: Chao Xu Date: Tue, 16 May 2017 20:29:43 -0700 Subject: [PATCH 04/23] generated --- cmd/libs/go2idl/client-gen/BUILD | 1 + cmd/libs/go2idl/client-gen/generators/BUILD | 1 + .../go2idl/client-gen/generators/fake/BUILD | 1 + .../go2idl/client-gen/generators/scheme/BUILD | 1 + cmd/libs/go2idl/client-gen/path/BUILD | 27 +++++++++++++++++++ hack/.linted_packages | 1 + 6 files changed, 32 insertions(+) create mode 100644 cmd/libs/go2idl/client-gen/path/BUILD diff --git a/cmd/libs/go2idl/client-gen/BUILD b/cmd/libs/go2idl/client-gen/BUILD index 81dd0f7b2a1..6a13076b0fb 100644 --- a/cmd/libs/go2idl/client-gen/BUILD +++ b/cmd/libs/go2idl/client-gen/BUILD @@ -41,6 +41,7 @@ filegroup( ":package-srcs", "//cmd/libs/go2idl/client-gen/args:all-srcs", "//cmd/libs/go2idl/client-gen/generators:all-srcs", + "//cmd/libs/go2idl/client-gen/path:all-srcs", "//cmd/libs/go2idl/client-gen/test_apis/testgroup:all-srcs", "//cmd/libs/go2idl/client-gen/testoutput/clientset_generated/test_internalclientset:all-srcs", "//cmd/libs/go2idl/client-gen/types:all-srcs", diff --git a/cmd/libs/go2idl/client-gen/generators/BUILD b/cmd/libs/go2idl/client-gen/generators/BUILD index dd089164209..72b1eb726b1 100644 --- a/cmd/libs/go2idl/client-gen/generators/BUILD +++ b/cmd/libs/go2idl/client-gen/generators/BUILD @@ -22,6 +22,7 @@ go_library( "//cmd/libs/go2idl/client-gen/args:go_default_library", "//cmd/libs/go2idl/client-gen/generators/fake:go_default_library", "//cmd/libs/go2idl/client-gen/generators/scheme:go_default_library", + "//cmd/libs/go2idl/client-gen/path:go_default_library", "//cmd/libs/go2idl/client-gen/types:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/gengo/args:go_default_library", diff --git a/cmd/libs/go2idl/client-gen/generators/fake/BUILD b/cmd/libs/go2idl/client-gen/generators/fake/BUILD index cad1c60c1a6..08ad1ac1daa 100644 --- a/cmd/libs/go2idl/client-gen/generators/fake/BUILD +++ b/cmd/libs/go2idl/client-gen/generators/fake/BUILD @@ -19,6 +19,7 @@ go_library( deps = [ "//cmd/libs/go2idl/client-gen/args:go_default_library", "//cmd/libs/go2idl/client-gen/generators/scheme:go_default_library", + "//cmd/libs/go2idl/client-gen/path:go_default_library", "//cmd/libs/go2idl/client-gen/types:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/gengo/generator:go_default_library", diff --git a/cmd/libs/go2idl/client-gen/generators/scheme/BUILD b/cmd/libs/go2idl/client-gen/generators/scheme/BUILD index de5835d84a2..d3f058386c8 100644 --- a/cmd/libs/go2idl/client-gen/generators/scheme/BUILD +++ b/cmd/libs/go2idl/client-gen/generators/scheme/BUILD @@ -12,6 +12,7 @@ go_library( srcs = ["generator_for_scheme.go"], tags = ["automanaged"], deps = [ + "//cmd/libs/go2idl/client-gen/path:go_default_library", "//cmd/libs/go2idl/client-gen/types:go_default_library", "//vendor/k8s.io/gengo/generator:go_default_library", "//vendor/k8s.io/gengo/namer:go_default_library", diff --git a/cmd/libs/go2idl/client-gen/path/BUILD b/cmd/libs/go2idl/client-gen/path/BUILD new file mode 100644 index 00000000000..1c967c9b846 --- /dev/null +++ b/cmd/libs/go2idl/client-gen/path/BUILD @@ -0,0 +1,27 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = ["path.go"], + tags = ["automanaged"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/hack/.linted_packages b/hack/.linted_packages index c2b1cf655d6..b215082a27b 100644 --- a/hack/.linted_packages +++ b/hack/.linted_packages @@ -26,6 +26,7 @@ cmd/kubectl cmd/kubelet cmd/libs/go2idl/client-gen cmd/libs/go2idl/client-gen/generators +cmd/libs/go2idl/client-gen/path cmd/libs/go2idl/client-gen/test_apis/testgroup/install cmd/libs/go2idl/conversion-gen cmd/libs/go2idl/deepcopy-gen From dd93784b2018a9da225badb4f00d73d626d238ac Mon Sep 17 00:00:00 2001 From: Cao Shufeng Date: Wed, 17 May 2017 17:23:44 +0800 Subject: [PATCH 05/23] remove useless flags from hack/verify-flags/known-flags.txt Flags in known-flags.txt is used to check misspelling from "-" to "_" in workspace, so a flag with out "-" should not show up in this file. --- hack/verify-flags/known-flags.txt | 9 --------- 1 file changed, 9 deletions(-) diff --git a/hack/verify-flags/known-flags.txt b/hack/verify-flags/known-flags.txt index 5aa027c0808..aa9bf6c5951 100644 --- a/hack/verify-flags/known-flags.txt +++ b/hack/verify-flags/known-flags.txt @@ -86,7 +86,6 @@ cgroups-per-qos chaos-chance cidr-allocator-type clean-start -cleanup cleanup-iptables client-ca-file client-certificate @@ -274,7 +273,6 @@ federation-name federation-system-namespace federation-upgrade-target file-check-frequency -file_content_in_loop file-suffix flex-volume-plugin-dir forward-services @@ -303,7 +301,6 @@ google-json-key grace-period ha-domain hairpin-mode -hard hard-pod-affinity-symmetric-weight healthz-bind-address healthz-port @@ -564,7 +561,6 @@ pv-recycler-minimum-timeout-nfs pv-recycler-pod-template-filepath-hostpath pv-recycler-pod-template-filepath-nfs pv-recycler-timeout-increment-hostpath -quiet read-only-port really-crash-for-testing reconcile-cidr @@ -593,13 +589,11 @@ requestheader-username-headers required-contexts require-kubeconfig resolv-conf -resource resource-container resource-name resource-quota-sync-period resource-version results-dir -retry_time rkt-api-endpoint rkt-path rkt-stage1-image @@ -618,7 +612,6 @@ schedule-pods-here scheduler-config scheduler-name schema-cache-dir -scopes scrape-timeout seccomp-profile-root secondary-node-eviction-rate @@ -669,7 +662,6 @@ storage-media-type storage-version storage-versions streaming-connection-idle-timeout -subresource suicide-timeout sync-frequency system-cgroups @@ -712,7 +704,6 @@ use-service-account-credentials user-whitelist use-service-account-credentials use-taint-based-evictions -verb verify-only versioned-clientset-package viper-config From 65342a00005250407fe591c57da1842b93c8431d Mon Sep 17 00:00:00 2001 From: Paul Michali Date: Tue, 16 May 2017 20:53:06 +0000 Subject: [PATCH 06/23] IPv6 support for hexCIDR() Includes these changes: - Modified so that IPv6 CIDRs can be converted correctly. - Added test cases for IPv6 addresses. - Split UTs for hexCIDR() and asciiCIDR() so that masking can be tested. - Add UTs for failure cases. Note: Some code that calls hexCIDR() builds a CIDR from the pod IP string and the concatenation of "/32". These should, in the future, use "128", if/when the pod IP is IPv6. Not addressed as part of this commit. --- pkg/util/bandwidth/linux.go | 5 +- pkg/util/bandwidth/linux_test.go | 85 +++++++++++++++++++++++++++++--- 2 files changed, 81 insertions(+), 9 deletions(-) diff --git a/pkg/util/bandwidth/linux.go b/pkg/util/bandwidth/linux.go index 6413930af39..9949aa3d179 100644 --- a/pkg/util/bandwidth/linux.go +++ b/pkg/util/bandwidth/linux.go @@ -101,7 +101,7 @@ func hexCIDR(cidr string) (string, error) { return "", err } ip = ip.Mask(ipnet.Mask) - hexIP := hex.EncodeToString([]byte(ip.To4())) + hexIP := hex.EncodeToString([]byte(ip)) hexMask := ipnet.Mask.String() return hexIP + "/" + hexMask, nil } @@ -119,6 +119,9 @@ func asciiCIDR(cidr string) (string, error) { ip := net.IP(ipData) maskData, err := hex.DecodeString(parts[1]) + if err != nil { + return "", err + } mask := net.IPMask(maskData) size, _ := mask.Size() diff --git a/pkg/util/bandwidth/linux_test.go b/pkg/util/bandwidth/linux_test.go index e005d65427e..980f8f845c7 100644 --- a/pkg/util/bandwidth/linux_test.go +++ b/pkg/util/bandwidth/linux_test.go @@ -94,19 +94,33 @@ func TestNextClassID(t *testing.T) { func TestHexCIDR(t *testing.T) { tests := []struct { + name string input string output string expectErr bool }{ { - input: "1.2.0.0/16", + name: "IPv4 masked", + input: "1.2.3.4/16", output: "01020000/ffff0000", }, { + name: "IPv4 host", input: "172.17.0.2/32", output: "ac110002/ffffffff", }, { + name: "IPv6 masked", + input: "2001:dead:beef::cafe/64", + output: "2001deadbeef00000000000000000000/ffffffffffffffff0000000000000000", + }, + { + name: "IPv6 host", + input: "2001::5/128", + output: "20010000000000000000000000000005/ffffffffffffffffffffffffffffffff", + }, + { + name: "invalid CIDR", input: "foo", expectErr: true, }, @@ -115,21 +129,76 @@ func TestHexCIDR(t *testing.T) { output, err := hexCIDR(test.input) if test.expectErr { if err == nil { - t.Error("unexpected non-error") + t.Errorf("case %s: unexpected non-error", test.name) } } else { if err != nil { - t.Errorf("unexpected error: %v", err) + t.Errorf("case %s: unexpected error: %v", test.name, err) } if output != test.output { - t.Errorf("expected: %s, saw: %s", test.output, output) + t.Errorf("case %s: expected: %s, saw: %s", + test.name, test.output, output) } - input, err := asciiCIDR(output) + } + } +} + +func TestAsciiCIDR(t *testing.T) { + tests := []struct { + name string + input string + output string + expectErr bool + }{ + { + name: "IPv4", + input: "01020000/ffff0000", + output: "1.2.0.0/16", + }, + { + name: "IPv4 host", + input: "ac110002/ffffffff", + output: "172.17.0.2/32", + }, + { + name: "IPv6", + input: "2001deadbeef00000000000000000000/ffffffffffffffff0000000000000000", + output: "2001:dead:beef::/64", + }, + { + name: "IPv6 host", + input: "20010000000000000000000000000005/ffffffffffffffffffffffffffffffff", + output: "2001::5/128", + }, + { + name: "invalid CIDR", + input: "malformed", + expectErr: true, + }, + { + name: "non-hex IP", + input: "nonhex/32", + expectErr: true, + }, + { + name: "non-hex mask", + input: "01020000/badmask", + expectErr: true, + }, + } + for _, test := range tests { + output, err := asciiCIDR(test.input) + if test.expectErr { + if err == nil { + t.Errorf("case %s: unexpected non-error", test.name) + } + } else { if err != nil { - t.Errorf("unexpected error: %v", err) + t.Errorf("case %s: unexpected error: %v", test.name, err) } - if input != test.input { - t.Errorf("expected: %s, saw: %s", test.input, input) + if output != test.output { + t.Errorf("case %s: expected: %s, saw: %s", + test.name, test.output, output) } } } From 87a5edd2cd4474213e84ff29a928a9ab514f8ef5 Mon Sep 17 00:00:00 2001 From: Nick Sardo Date: Wed, 17 May 2017 14:38:25 -0700 Subject: [PATCH 07/23] Initialize cloud providers with a K8s clientBuilder --- cmd/cloud-controller-manager/app/controllermanager.go | 6 ++++++ cmd/kube-controller-manager/app/controllermanager.go | 5 +++++ pkg/cloudprovider/BUILD | 1 + pkg/cloudprovider/cloud.go | 3 +++ pkg/cloudprovider/providers/aws/BUILD | 1 + pkg/cloudprovider/providers/aws/aws.go | 4 ++++ pkg/cloudprovider/providers/azure/BUILD | 1 + pkg/cloudprovider/providers/azure/azure.go | 6 +++++- pkg/cloudprovider/providers/cloudstack/BUILD | 1 + pkg/cloudprovider/providers/cloudstack/cloudstack.go | 4 ++++ pkg/cloudprovider/providers/fake/BUILD | 1 + pkg/cloudprovider/providers/fake/fake.go | 4 ++++ pkg/cloudprovider/providers/gce/BUILD | 1 + pkg/cloudprovider/providers/gce/gce.go | 4 ++++ pkg/cloudprovider/providers/mesos/BUILD | 1 + pkg/cloudprovider/providers/mesos/mesos.go | 4 ++++ pkg/cloudprovider/providers/openstack/BUILD | 1 + pkg/cloudprovider/providers/openstack/openstack.go | 4 ++++ pkg/cloudprovider/providers/ovirt/BUILD | 1 + pkg/cloudprovider/providers/ovirt/ovirt.go | 6 +++++- pkg/cloudprovider/providers/photon/BUILD | 1 + pkg/cloudprovider/providers/photon/photon.go | 4 ++++ pkg/cloudprovider/providers/rackspace/BUILD | 1 + pkg/cloudprovider/providers/rackspace/rackspace.go | 4 ++++ pkg/cloudprovider/providers/vsphere/BUILD | 1 + pkg/cloudprovider/providers/vsphere/vsphere.go | 4 ++++ 26 files changed, 72 insertions(+), 2 deletions(-) diff --git a/cmd/cloud-controller-manager/app/controllermanager.go b/cmd/cloud-controller-manager/app/controllermanager.go index 5cc76b5b795..e2defbcb917 100644 --- a/cmd/cloud-controller-manager/app/controllermanager.go +++ b/cmd/cloud-controller-manager/app/controllermanager.go @@ -199,6 +199,12 @@ func StartControllers(s *options.CloudControllerManagerServer, kubeconfig *restc client := func(serviceAccountName string) clientset.Interface { return rootClientBuilder.ClientOrDie(serviceAccountName) } + + if cloud != nil { + // Initialize the cloud provider with a reference to the clientBuilder + cloud.Initialize(clientBuilder) + } + versionedClient := client("shared-informers") sharedInformers := informers.NewSharedInformerFactory(versionedClient, resyncPeriod(s)()) diff --git a/cmd/kube-controller-manager/app/controllermanager.go b/cmd/kube-controller-manager/app/controllermanager.go index c6fab913f97..9f347136ee0 100644 --- a/cmd/kube-controller-manager/app/controllermanager.go +++ b/cmd/kube-controller-manager/app/controllermanager.go @@ -451,6 +451,11 @@ func StartControllers(controllers map[string]InitFunc, s *options.CMServer, root return fmt.Errorf("cloud provider could not be initialized: %v", err) } + if cloud != nil { + // Initialize the cloud provider with a reference to the clientBuilder + cloud.Initialize(clientBuilder) + } + if ctx.IsControllerEnabled(nodeControllerName) { _, clusterCIDR, err := net.ParseCIDR(s.ClusterCIDR) if err != nil { diff --git a/pkg/cloudprovider/BUILD b/pkg/cloudprovider/BUILD index 134484b5e9c..e638a06508e 100644 --- a/pkg/cloudprovider/BUILD +++ b/pkg/cloudprovider/BUILD @@ -17,6 +17,7 @@ go_library( tags = ["automanaged"], deps = [ "//pkg/api/v1:go_default_library", + "//pkg/controller:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", ], diff --git a/pkg/cloudprovider/cloud.go b/pkg/cloudprovider/cloud.go index 2810ab3019f..a9217e21bec 100644 --- a/pkg/cloudprovider/cloud.go +++ b/pkg/cloudprovider/cloud.go @@ -23,10 +23,13 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/controller" ) // Interface is an abstract, pluggable interface for cloud providers. type Interface interface { + // Initialize provides the cloud with a kubernetes client builder + Initialize(clientBuilder controller.ControllerClientBuilder) // LoadBalancer returns a balancer interface. Also returns true if the interface is supported, false otherwise. LoadBalancer() (LoadBalancer, bool) // Instances returns an instances interface. Also returns true if the interface is supported, false otherwise. diff --git a/pkg/cloudprovider/providers/aws/BUILD b/pkg/cloudprovider/providers/aws/BUILD index 47e16a82c45..0a0673595cb 100644 --- a/pkg/cloudprovider/providers/aws/BUILD +++ b/pkg/cloudprovider/providers/aws/BUILD @@ -30,6 +30,7 @@ go_library( "//pkg/api/v1:go_default_library", "//pkg/api/v1/service:go_default_library", "//pkg/cloudprovider:go_default_library", + "//pkg/controller:go_default_library", "//pkg/credentialprovider/aws:go_default_library", "//pkg/volume:go_default_library", "//vendor/github.com/aws/aws-sdk-go/aws:go_default_library", diff --git a/pkg/cloudprovider/providers/aws/aws.go b/pkg/cloudprovider/providers/aws/aws.go index 1667ff015a4..8d42884e146 100644 --- a/pkg/cloudprovider/providers/aws/aws.go +++ b/pkg/cloudprovider/providers/aws/aws.go @@ -49,6 +49,7 @@ import ( "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/api/v1/service" "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/volume" ) @@ -888,6 +889,9 @@ func newAWSCloud(config io.Reader, awsServices Services) (*Cloud, error) { return awsCloud, nil } +// Initialize passes a Kubernetes clientBuilder interface to the cloud provider +func (c *Cloud) Initialize(clientBuilder controller.ControllerClientBuilder) {} + // Clusters returns the list of clusters. func (c *Cloud) Clusters() (cloudprovider.Clusters, bool) { return nil, false diff --git a/pkg/cloudprovider/providers/azure/BUILD b/pkg/cloudprovider/providers/azure/BUILD index 5192a8b5e93..3c7707158c1 100644 --- a/pkg/cloudprovider/providers/azure/BUILD +++ b/pkg/cloudprovider/providers/azure/BUILD @@ -29,6 +29,7 @@ go_library( "//pkg/api/v1:go_default_library", "//pkg/api/v1/service:go_default_library", "//pkg/cloudprovider:go_default_library", + "//pkg/controller:go_default_library", "//pkg/version:go_default_library", "//pkg/volume:go_default_library", "//vendor/github.com/Azure/azure-sdk-for-go/arm/compute:go_default_library", diff --git a/pkg/cloudprovider/providers/azure/azure.go b/pkg/cloudprovider/providers/azure/azure.go index cd22f7beb5e..7694693e261 100644 --- a/pkg/cloudprovider/providers/azure/azure.go +++ b/pkg/cloudprovider/providers/azure/azure.go @@ -20,8 +20,10 @@ import ( "fmt" "io" "io/ioutil" + "time" "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/version" "github.com/Azure/azure-sdk-for-go/arm/compute" @@ -30,7 +32,6 @@ import ( "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/azure" "github.com/ghodss/yaml" - "time" ) // CloudProviderName is the value used for the --cloud-provider flag @@ -179,6 +180,9 @@ func NewCloud(configReader io.Reader) (cloudprovider.Interface, error) { return &az, nil } +// Initialize passes a Kubernetes clientBuilder interface to the cloud provider +func (az *Cloud) Initialize(clientBuilder controller.ControllerClientBuilder) {} + // LoadBalancer returns a balancer interface. Also returns true if the interface is supported, false otherwise. func (az *Cloud) LoadBalancer() (cloudprovider.LoadBalancer, bool) { return az, true diff --git a/pkg/cloudprovider/providers/cloudstack/BUILD b/pkg/cloudprovider/providers/cloudstack/BUILD index 10928e0f0ad..52428e7e25e 100644 --- a/pkg/cloudprovider/providers/cloudstack/BUILD +++ b/pkg/cloudprovider/providers/cloudstack/BUILD @@ -18,6 +18,7 @@ go_library( deps = [ "//pkg/api/v1:go_default_library", "//pkg/cloudprovider:go_default_library", + "//pkg/controller:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/xanzy/go-cloudstack/cloudstack:go_default_library", "//vendor/gopkg.in/gcfg.v1:go_default_library", diff --git a/pkg/cloudprovider/providers/cloudstack/cloudstack.go b/pkg/cloudprovider/providers/cloudstack/cloudstack.go index 308e6a85f6d..f8e2b3ffb6f 100644 --- a/pkg/cloudprovider/providers/cloudstack/cloudstack.go +++ b/pkg/cloudprovider/providers/cloudstack/cloudstack.go @@ -24,6 +24,7 @@ import ( "github.com/xanzy/go-cloudstack/cloudstack" "gopkg.in/gcfg.v1" "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/controller" ) // ProviderName is the name of this cloud provider. @@ -81,6 +82,9 @@ func newCSCloud(cfg *CSConfig) (*CSCloud, error) { return &CSCloud{client, cfg.Global.ProjectID, cfg.Global.Zone}, nil } +// Initialize passes a Kubernetes clientBuilder interface to the cloud provider +func (cs *CSCloud) Initialize(clientBuilder controller.ControllerClientBuilder) {} + // LoadBalancer returns an implementation of LoadBalancer for CloudStack. func (cs *CSCloud) LoadBalancer() (cloudprovider.LoadBalancer, bool) { return cs, true diff --git a/pkg/cloudprovider/providers/fake/BUILD b/pkg/cloudprovider/providers/fake/BUILD index 5b586ac810a..489d7312e77 100644 --- a/pkg/cloudprovider/providers/fake/BUILD +++ b/pkg/cloudprovider/providers/fake/BUILD @@ -17,6 +17,7 @@ go_library( deps = [ "//pkg/api/v1:go_default_library", "//pkg/cloudprovider:go_default_library", + "//pkg/controller:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", ], ) diff --git a/pkg/cloudprovider/providers/fake/fake.go b/pkg/cloudprovider/providers/fake/fake.go index d7b59ac7ffa..dcca77f5118 100644 --- a/pkg/cloudprovider/providers/fake/fake.go +++ b/pkg/cloudprovider/providers/fake/fake.go @@ -26,6 +26,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/controller" ) const defaultProviderName = "fake" @@ -82,6 +83,9 @@ func (f *FakeCloud) ClearCalls() { f.Calls = []string{} } +// Initialize passes a Kubernetes clientBuilder interface to the cloud provider +func (f *FakeCloud) Initialize(clientBuilder controller.ControllerClientBuilder) {} + func (f *FakeCloud) ListClusters() ([]string, error) { return f.ClusterList, f.Err } diff --git a/pkg/cloudprovider/providers/gce/BUILD b/pkg/cloudprovider/providers/gce/BUILD index 726e4681865..5e120829516 100644 --- a/pkg/cloudprovider/providers/gce/BUILD +++ b/pkg/cloudprovider/providers/gce/BUILD @@ -38,6 +38,7 @@ go_library( "//pkg/api/v1:go_default_library", "//pkg/api/v1/service:go_default_library", "//pkg/cloudprovider:go_default_library", + "//pkg/controller:go_default_library", "//pkg/util/net/sets:go_default_library", "//pkg/volume:go_default_library", "//vendor/cloud.google.com/go/compute/metadata:go_default_library", diff --git a/pkg/cloudprovider/providers/gce/gce.go b/pkg/cloudprovider/providers/gce/gce.go index a878ee23926..a1a026971a1 100644 --- a/pkg/cloudprovider/providers/gce/gce.go +++ b/pkg/cloudprovider/providers/gce/gce.go @@ -31,6 +31,7 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/util/flowcontrol" "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/controller" "github.com/golang/glog" "golang.org/x/oauth2" @@ -233,6 +234,9 @@ func CreateGCECloud(projectID, region, zone string, managedZones []string, netwo }, nil } +// Initialize passes a Kubernetes clientBuilder interface to the cloud provider +func (gce *GCECloud) Initialize(clientBuilder controller.ControllerClientBuilder) {} + // LoadBalancer returns an implementation of LoadBalancer for Google Compute Engine. func (gce *GCECloud) LoadBalancer() (cloudprovider.LoadBalancer, bool) { return gce, true diff --git a/pkg/cloudprovider/providers/mesos/BUILD b/pkg/cloudprovider/providers/mesos/BUILD index 8b4b9d7f52d..97344163654 100644 --- a/pkg/cloudprovider/providers/mesos/BUILD +++ b/pkg/cloudprovider/providers/mesos/BUILD @@ -20,6 +20,7 @@ go_library( deps = [ "//pkg/api/v1:go_default_library", "//pkg/cloudprovider:go_default_library", + "//pkg/controller:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/mesos/mesos-go/detector:go_default_library", "//vendor/github.com/mesos/mesos-go/detector/zoo:go_default_library", diff --git a/pkg/cloudprovider/providers/mesos/mesos.go b/pkg/cloudprovider/providers/mesos/mesos.go index 76d80e36c03..9462ff0a050 100644 --- a/pkg/cloudprovider/providers/mesos/mesos.go +++ b/pkg/cloudprovider/providers/mesos/mesos.go @@ -31,6 +31,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/controller" ) const ( @@ -89,6 +90,9 @@ func newMesosCloud(configReader io.Reader) (*MesosCloud, error) { } } +// Initialize passes a Kubernetes clientBuilder interface to the cloud provider +func (c *MesosCloud) Initialize(clientBuilder controller.ControllerClientBuilder) {} + // Implementation of Instances.CurrentNodeName func (c *MesosCloud) CurrentNodeName(hostname string) (types.NodeName, error) { return types.NodeName(hostname), nil diff --git a/pkg/cloudprovider/providers/openstack/BUILD b/pkg/cloudprovider/providers/openstack/BUILD index 53f86b4ee89..e10cb26aa08 100644 --- a/pkg/cloudprovider/providers/openstack/BUILD +++ b/pkg/cloudprovider/providers/openstack/BUILD @@ -24,6 +24,7 @@ go_library( "//pkg/api/v1/helper:go_default_library", "//pkg/api/v1/service:go_default_library", "//pkg/cloudprovider:go_default_library", + "//pkg/controller:go_default_library", "//pkg/util/exec:go_default_library", "//pkg/util/mount:go_default_library", "//pkg/volume:go_default_library", diff --git a/pkg/cloudprovider/providers/openstack/openstack.go b/pkg/cloudprovider/providers/openstack/openstack.go index 4ad0bfd772e..26ce40dcb52 100644 --- a/pkg/cloudprovider/providers/openstack/openstack.go +++ b/pkg/cloudprovider/providers/openstack/openstack.go @@ -45,6 +45,7 @@ import ( "k8s.io/kubernetes/pkg/api/v1" v1helper "k8s.io/kubernetes/pkg/api/v1/helper" "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/controller" ) const ProviderName = "openstack" @@ -265,6 +266,9 @@ func newOpenStack(cfg Config) (*OpenStack, error) { return &os, nil } +// Initialize passes a Kubernetes clientBuilder interface to the cloud provider +func (os *OpenStack) Initialize(clientBuilder controller.ControllerClientBuilder) {} + // mapNodeNameToServerName maps a k8s NodeName to an OpenStack Server Name // This is a simple string cast. func mapNodeNameToServerName(nodeName types.NodeName) string { diff --git a/pkg/cloudprovider/providers/ovirt/BUILD b/pkg/cloudprovider/providers/ovirt/BUILD index fcbc3e9f4d6..ce90c4e1edf 100644 --- a/pkg/cloudprovider/providers/ovirt/BUILD +++ b/pkg/cloudprovider/providers/ovirt/BUILD @@ -15,6 +15,7 @@ go_library( deps = [ "//pkg/api/v1:go_default_library", "//pkg/cloudprovider:go_default_library", + "//pkg/controller:go_default_library", "//vendor/gopkg.in/gcfg.v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", ], diff --git a/pkg/cloudprovider/providers/ovirt/ovirt.go b/pkg/cloudprovider/providers/ovirt/ovirt.go index b0257cbc83c..8273c0b170e 100644 --- a/pkg/cloudprovider/providers/ovirt/ovirt.go +++ b/pkg/cloudprovider/providers/ovirt/ovirt.go @@ -34,6 +34,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/controller" ) const ProviderName = "ovirt" @@ -116,7 +117,10 @@ func newOVirtCloud(config io.Reader) (*OVirtCloud, error) { return &OVirtCloud{VmsRequest: request}, nil } -func (aws *OVirtCloud) Clusters() (cloudprovider.Clusters, bool) { +// Initialize passes a Kubernetes clientBuilder interface to the cloud provider +func (v *OVirtCloud) Initialize(clientBuilder controller.ControllerClientBuilder) {} + +func (v *OVirtCloud) Clusters() (cloudprovider.Clusters, bool) { return nil, false } diff --git a/pkg/cloudprovider/providers/photon/BUILD b/pkg/cloudprovider/providers/photon/BUILD index d367e181b44..85c17eb901d 100644 --- a/pkg/cloudprovider/providers/photon/BUILD +++ b/pkg/cloudprovider/providers/photon/BUILD @@ -16,6 +16,7 @@ go_library( "//pkg/api/v1:go_default_library", "//pkg/api/v1/helper:go_default_library", "//pkg/cloudprovider:go_default_library", + "//pkg/controller:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/vmware/photon-controller-go-sdk/photon:go_default_library", "//vendor/gopkg.in/gcfg.v1:go_default_library", diff --git a/pkg/cloudprovider/providers/photon/photon.go b/pkg/cloudprovider/providers/photon/photon.go index c2bb0265361..f33e9c27a89 100644 --- a/pkg/cloudprovider/providers/photon/photon.go +++ b/pkg/cloudprovider/providers/photon/photon.go @@ -41,6 +41,7 @@ import ( "k8s.io/kubernetes/pkg/api/v1" v1helper "k8s.io/kubernetes/pkg/api/v1/helper" "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/controller" ) const ( @@ -292,6 +293,9 @@ func newPCCloud(cfg PCConfig) (*PCCloud, error) { return &pc, nil } +// Initialize passes a Kubernetes clientBuilder interface to the cloud provider +func (pc *PCCloud) Initialize(clientBuilder controller.ControllerClientBuilder) {} + // Instances returns an implementation of Instances for Photon Controller. func (pc *PCCloud) Instances() (cloudprovider.Instances, bool) { return pc, true diff --git a/pkg/cloudprovider/providers/rackspace/BUILD b/pkg/cloudprovider/providers/rackspace/BUILD index dceda3955c7..e9c9aae426e 100644 --- a/pkg/cloudprovider/providers/rackspace/BUILD +++ b/pkg/cloudprovider/providers/rackspace/BUILD @@ -15,6 +15,7 @@ go_library( deps = [ "//pkg/api/v1:go_default_library", "//pkg/cloudprovider:go_default_library", + "//pkg/controller:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/rackspace/gophercloud:go_default_library", "//vendor/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach:go_default_library", diff --git a/pkg/cloudprovider/providers/rackspace/rackspace.go b/pkg/cloudprovider/providers/rackspace/rackspace.go index 253b0508870..c333b2f41c5 100644 --- a/pkg/cloudprovider/providers/rackspace/rackspace.go +++ b/pkg/cloudprovider/providers/rackspace/rackspace.go @@ -43,6 +43,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/controller" ) const ( @@ -218,6 +219,9 @@ func newRackspace(cfg Config) (*Rackspace, error) { return &os, nil } +// Initialize passes a Kubernetes clientBuilder interface to the cloud provider +func (os *Rackspace) Initialize(clientBuilder controller.ControllerClientBuilder) {} + type Instances struct { compute *gophercloud.ServiceClient } diff --git a/pkg/cloudprovider/providers/vsphere/BUILD b/pkg/cloudprovider/providers/vsphere/BUILD index fd29342072e..8ff6d52ce42 100644 --- a/pkg/cloudprovider/providers/vsphere/BUILD +++ b/pkg/cloudprovider/providers/vsphere/BUILD @@ -19,6 +19,7 @@ go_library( "//pkg/api/v1:go_default_library", "//pkg/api/v1/helper:go_default_library", "//pkg/cloudprovider:go_default_library", + "//pkg/controller:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/vmware/govmomi:go_default_library", "//vendor/github.com/vmware/govmomi/find:go_default_library", diff --git a/pkg/cloudprovider/providers/vsphere/vsphere.go b/pkg/cloudprovider/providers/vsphere/vsphere.go index 14a2628e295..a252e9748bb 100644 --- a/pkg/cloudprovider/providers/vsphere/vsphere.go +++ b/pkg/cloudprovider/providers/vsphere/vsphere.go @@ -50,6 +50,7 @@ import ( "k8s.io/kubernetes/pkg/api/v1" v1helper "k8s.io/kubernetes/pkg/api/v1/helper" "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/controller" ) const ( @@ -229,6 +230,9 @@ func init() { }) } +// Initialize passes a Kubernetes clientBuilder interface to the cloud provider +func (vs *VSphere) Initialize(clientBuilder controller.ControllerClientBuilder) {} + // UUID gets the BIOS UUID via the sys interface. This UUID is known by vsphere func getvmUUID() (string, error) { id, err := ioutil.ReadFile(UUIDPath) From e70d59063f8171bd659cb3e2a33aa55630de0644 Mon Sep 17 00:00:00 2001 From: Phillip Wittrock Date: Tue, 16 May 2017 15:30:27 -0700 Subject: [PATCH 08/23] Redirect users filing kubectl issues to the kubernetes/kubectl repo --- .github/ISSUE_TEMPLATE.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index e8e3a28a592..b0ee15def24 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -2,6 +2,11 @@ **Is this a request for help?** (If yes, you should use our troubleshooting guide and community support channels, see http://kubernetes.io/docs/troubleshooting/.): +**Note:** Please file issues for subcomponents under the appropriate repo + +| Component | Repo | +| --------- | ------------------------------------------------------------------ | +| kubectl | [kubernetes/kubectl](https://github.com/kubernetes/kubectl/issues/new) | **What keywords did you search in Kubernetes issues before filing this one?** (If you have found any duplicates, you should instead reply there.): From 5d1afb4933d44e502765c3ed8fb5147c2a7e333b Mon Sep 17 00:00:00 2001 From: deads2k Date: Wed, 17 May 2017 15:18:31 -0400 Subject: [PATCH 09/23] add CRD finalizer to remove CRs --- .../customresource-01/noxu-apiservice.yaml | 2 +- .../artifacts/example/apiservice.yaml | 2 +- .../pkg/apis/apiextensions/BUILD | 9 + .../pkg/apis/apiextensions/helpers.go | 59 +++- .../pkg/apis/apiextensions/helpers_test.go | 90 +++++ .../pkg/apis/apiextensions/types.go | 4 + .../pkg/apis/apiextensions/v1alpha1/types.go | 4 + .../pkg/apiserver/BUILD | 2 + .../pkg/apiserver/apiserver.go | 7 + .../customresource_discovery_controller.go | 8 +- .../pkg/apiserver/customresource_handler.go | 19 + .../pkg/controller/finalizer/BUILD | 34 ++ .../pkg/controller/finalizer/crd_finalizer.go | 333 ++++++++++++++++++ .../registry/customresourcedefinition/BUILD | 3 + .../registry/customresourcedefinition/etcd.go | 87 +++++ .../customresourcedefinition/strategy.go | 1 - .../test/integration/registration_test.go | 5 - 17 files changed, 647 insertions(+), 22 deletions(-) create mode 100644 staging/src/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/helpers_test.go create mode 100644 staging/src/k8s.io/kube-apiextensions-server/pkg/controller/finalizer/BUILD create mode 100644 staging/src/k8s.io/kube-apiextensions-server/pkg/controller/finalizer/crd_finalizer.go diff --git a/staging/src/k8s.io/kube-apiextensions-server/artifacts/customresource-01/noxu-apiservice.yaml b/staging/src/k8s.io/kube-apiextensions-server/artifacts/customresource-01/noxu-apiservice.yaml index 148ec4ef339..a11b3048a80 100644 --- a/staging/src/k8s.io/kube-apiextensions-server/artifacts/customresource-01/noxu-apiservice.yaml +++ b/staging/src/k8s.io/kube-apiextensions-server/artifacts/customresource-01/noxu-apiservice.yaml @@ -1,4 +1,4 @@ -apiVersion: apiregistration.k8s.io/v1alpha1 +apiVersion: apiregistration.k8s.io/v1beta1 kind: APIService metadata: name: v1alpha1.mygroup.example.com diff --git a/staging/src/k8s.io/kube-apiextensions-server/artifacts/example/apiservice.yaml b/staging/src/k8s.io/kube-apiextensions-server/artifacts/example/apiservice.yaml index 3ede9152ac6..820a5fa598c 100644 --- a/staging/src/k8s.io/kube-apiextensions-server/artifacts/example/apiservice.yaml +++ b/staging/src/k8s.io/kube-apiextensions-server/artifacts/example/apiservice.yaml @@ -1,4 +1,4 @@ -apiVersion: apiregistration.k8s.io/v1alpha1 +apiVersion: apiregistration.k8s.io/v1beta1 kind: APIService metadata: name: v1alpha1.apiextensions.k8s.io diff --git a/staging/src/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/BUILD b/staging/src/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/BUILD index 0e128d1a316..c424286b114 100644 --- a/staging/src/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/BUILD +++ b/staging/src/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/BUILD @@ -5,6 +5,7 @@ licenses(["notice"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", + "go_test", ) go_library( @@ -24,3 +25,11 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", ], ) + +go_test( + name = "go_default_test", + srcs = ["helpers_test.go"], + library = ":go_default_library", + tags = ["automanaged"], + deps = ["//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library"], +) diff --git a/staging/src/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/helpers.go b/staging/src/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/helpers.go index b495fd55d6f..c5731fca966 100644 --- a/staging/src/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/helpers.go +++ b/staging/src/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/helpers.go @@ -18,10 +18,10 @@ package apiextensions // SetCRDCondition sets the status condition. It either overwrites the existing one or // creates a new one -func SetCRDCondition(customResourceDefinition *CustomResourceDefinition, newCondition CustomResourceDefinitionCondition) { - existingCondition := FindCRDCondition(customResourceDefinition, newCondition.Type) +func SetCRDCondition(crd *CustomResourceDefinition, newCondition CustomResourceDefinitionCondition) { + existingCondition := FindCRDCondition(crd, newCondition.Type) if existingCondition == nil { - customResourceDefinition.Status.Conditions = append(customResourceDefinition.Status.Conditions, newCondition) + crd.Status.Conditions = append(crd.Status.Conditions, newCondition) return } @@ -34,11 +34,22 @@ func SetCRDCondition(customResourceDefinition *CustomResourceDefinition, newCond existingCondition.Message = newCondition.Message } +// RemoveCRDCondition removes the status condition. +func RemoveCRDCondition(crd *CustomResourceDefinition, conditionType CustomResourceDefinitionConditionType) { + newConditions := []CustomResourceDefinitionCondition{} + for _, condition := range crd.Status.Conditions { + if condition.Type != conditionType { + newConditions = append(newConditions, condition) + } + } + crd.Status.Conditions = newConditions +} + // FindCRDCondition returns the condition you're looking for or nil -func FindCRDCondition(customResourceDefinition *CustomResourceDefinition, conditionType CustomResourceDefinitionConditionType) *CustomResourceDefinitionCondition { - for i := range customResourceDefinition.Status.Conditions { - if customResourceDefinition.Status.Conditions[i].Type == conditionType { - return &customResourceDefinition.Status.Conditions[i] +func FindCRDCondition(crd *CustomResourceDefinition, conditionType CustomResourceDefinitionConditionType) *CustomResourceDefinitionCondition { + for i := range crd.Status.Conditions { + if crd.Status.Conditions[i].Type == conditionType { + return &crd.Status.Conditions[i] } } @@ -46,18 +57,18 @@ func FindCRDCondition(customResourceDefinition *CustomResourceDefinition, condit } // IsCRDConditionTrue indicates if the condition is present and strictly true -func IsCRDConditionTrue(customResourceDefinition *CustomResourceDefinition, conditionType CustomResourceDefinitionConditionType) bool { - return IsCRDConditionPresentAndEqual(customResourceDefinition, conditionType, ConditionTrue) +func IsCRDConditionTrue(crd *CustomResourceDefinition, conditionType CustomResourceDefinitionConditionType) bool { + return IsCRDConditionPresentAndEqual(crd, conditionType, ConditionTrue) } // IsCRDConditionFalse indicates if the condition is present and false true -func IsCRDConditionFalse(customResourceDefinition *CustomResourceDefinition, conditionType CustomResourceDefinitionConditionType) bool { - return IsCRDConditionPresentAndEqual(customResourceDefinition, conditionType, ConditionFalse) +func IsCRDConditionFalse(crd *CustomResourceDefinition, conditionType CustomResourceDefinitionConditionType) bool { + return IsCRDConditionPresentAndEqual(crd, conditionType, ConditionFalse) } // IsCRDConditionPresentAndEqual indicates if the condition is present and equal to the arg -func IsCRDConditionPresentAndEqual(customResourceDefinition *CustomResourceDefinition, conditionType CustomResourceDefinitionConditionType, status ConditionStatus) bool { - for _, condition := range customResourceDefinition.Status.Conditions { +func IsCRDConditionPresentAndEqual(crd *CustomResourceDefinition, conditionType CustomResourceDefinitionConditionType, status ConditionStatus) bool { + for _, condition := range crd.Status.Conditions { if condition.Type == conditionType { return condition.Status == status } @@ -76,3 +87,25 @@ func IsCRDConditionEquivalent(lhs, rhs *CustomResourceDefinitionCondition) bool return lhs.Message == rhs.Message && lhs.Reason == rhs.Reason && lhs.Status == rhs.Status && lhs.Type == rhs.Type } + +// CRDHasFinalizer returns true if the finalizer is in the list +func CRDHasFinalizer(crd *CustomResourceDefinition, needle string) bool { + for _, finalizer := range crd.Finalizers { + if finalizer == needle { + return true + } + } + + return false +} + +// CRDRemoveFinalizer removes the finalizer if present +func CRDRemoveFinalizer(crd *CustomResourceDefinition, needle string) { + newFinalizers := []string{} + for _, finalizer := range crd.Finalizers { + if finalizer != needle { + newFinalizers = append(newFinalizers, finalizer) + } + } + crd.Finalizers = newFinalizers +} diff --git a/staging/src/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/helpers_test.go b/staging/src/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/helpers_test.go new file mode 100644 index 00000000000..40e65b59a86 --- /dev/null +++ b/staging/src/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/helpers_test.go @@ -0,0 +1,90 @@ +/* +Copyright 2016 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 apiextensions + +import ( + "reflect" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestCRDHasFinalizer(t *testing.T) { + tests := []struct { + name string + crd *CustomResourceDefinition + finalizerToCheck string + + expected bool + }{ + { + name: "missing", + crd: &CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"not-it"}}, + }, + finalizerToCheck: "it", + expected: false, + }, + { + name: "present", + crd: &CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"not-it", "it"}}, + }, + finalizerToCheck: "it", + expected: true, + }, + } + for _, tc := range tests { + actual := CRDHasFinalizer(tc.crd, tc.finalizerToCheck) + if tc.expected != actual { + t.Errorf("%v expected %v, got %v", tc.name, tc.expected, actual) + } + } +} + +func TestCRDRemoveFinalizer(t *testing.T) { + tests := []struct { + name string + crd *CustomResourceDefinition + finalizerToCheck string + + expected []string + }{ + { + name: "missing", + crd: &CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"not-it"}}, + }, + finalizerToCheck: "it", + expected: []string{"not-it"}, + }, + { + name: "present", + crd: &CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"not-it", "it"}}, + }, + finalizerToCheck: "it", + expected: []string{"not-it"}, + }, + } + for _, tc := range tests { + CRDRemoveFinalizer(tc.crd, tc.finalizerToCheck) + if !reflect.DeepEqual(tc.expected, tc.crd.Finalizers) { + t.Errorf("%v expected %v, got %v", tc.name, tc.expected, tc.crd.Finalizers) + } + } +} diff --git a/staging/src/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/types.go b/staging/src/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/types.go index f802f0b5d58..c2012df173a 100644 --- a/staging/src/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/types.go +++ b/staging/src/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/types.go @@ -104,6 +104,10 @@ type CustomResourceDefinitionStatus struct { AcceptedNames CustomResourceDefinitionNames } +// CustomResourceCleanupFinalizer is the name of the finalizer which will delete instances of +// a CustomResourceDefinition +const CustomResourceCleanupFinalizer = "customresourcecleanup.apiextensions.k8s.io" + // +genclient=true // +nonNamespaced=true diff --git a/staging/src/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/v1alpha1/types.go b/staging/src/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/v1alpha1/types.go index c052e75079d..1195df29bb7 100644 --- a/staging/src/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/v1alpha1/types.go +++ b/staging/src/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/v1alpha1/types.go @@ -104,6 +104,10 @@ type CustomResourceDefinitionStatus struct { AcceptedNames CustomResourceDefinitionNames `json:"acceptedNames" protobuf:"bytes,2,opt,name=acceptedNames"` } +// CustomResourceCleanupFinalizer is the name of the finalizer which will delete instances of +// a CustomResourceDefinition +const CustomResourceCleanupFinalizer = "customresourcecleanup.apiextensions.k8s.io" + // +genclient=true // +nonNamespaced=true diff --git a/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/BUILD b/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/BUILD index 235e4f2263b..56a8d0a96af 100644 --- a/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/BUILD +++ b/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/BUILD @@ -43,6 +43,7 @@ go_library( "//vendor/k8s.io/apiserver/pkg/server:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library", "//vendor/k8s.io/client-go/discovery:go_default_library", + "//vendor/k8s.io/client-go/dynamic:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", "//vendor/k8s.io/client-go/util/workqueue:go_default_library", "//vendor/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions:go_default_library", @@ -54,6 +55,7 @@ go_library( "//vendor/k8s.io/kube-apiextensions-server/pkg/client/informers/internalversion:go_default_library", "//vendor/k8s.io/kube-apiextensions-server/pkg/client/informers/internalversion/apiextensions/internalversion:go_default_library", "//vendor/k8s.io/kube-apiextensions-server/pkg/client/listers/apiextensions/internalversion:go_default_library", + "//vendor/k8s.io/kube-apiextensions-server/pkg/controller/finalizer:go_default_library", "//vendor/k8s.io/kube-apiextensions-server/pkg/controller/status:go_default_library", "//vendor/k8s.io/kube-apiextensions-server/pkg/registry/customresource:go_default_library", "//vendor/k8s.io/kube-apiextensions-server/pkg/registry/customresourcedefinition:go_default_library", diff --git a/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/apiserver.go b/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/apiserver.go index 008dee48466..341afccfc3b 100644 --- a/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/apiserver.go +++ b/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/apiserver.go @@ -31,12 +31,14 @@ import ( genericregistry "k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" + "k8s.io/client-go/dynamic" "k8s.io/kube-apiextensions-server/pkg/apis/apiextensions" "k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/install" "k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/v1alpha1" "k8s.io/kube-apiextensions-server/pkg/client/clientset/internalclientset" internalinformers "k8s.io/kube-apiextensions-server/pkg/client/informers/internalversion" + "k8s.io/kube-apiextensions-server/pkg/controller/finalizer" "k8s.io/kube-apiextensions-server/pkg/controller/status" "k8s.io/kube-apiextensions-server/pkg/registry/customresourcedefinition" @@ -157,6 +159,10 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) customResourceDefinitionController := NewDiscoveryController(customResourceDefinitionInformers.Apiextensions().InternalVersion().CustomResourceDefinitions(), versionDiscoveryHandler, groupDiscoveryHandler) namingController := status.NewNamingConditionController(customResourceDefinitionInformers.Apiextensions().InternalVersion().CustomResourceDefinitions(), customResourceDefinitionClient) + finalizingController := finalizer.NewCRDFinalizer( + customResourceDefinitionInformers.Apiextensions().InternalVersion().CustomResourceDefinitions(), + customResourceDefinitionClient, + dynamic.NewDynamicClientPool(s.GenericAPIServer.LoopbackClientConfig)) s.GenericAPIServer.AddPostStartHook("start-apiextensions-informers", func(context genericapiserver.PostStartHookContext) error { customResourceDefinitionInformers.Start(context.StopCh) @@ -165,6 +171,7 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) s.GenericAPIServer.AddPostStartHook("start-apiextensions-controllers", func(context genericapiserver.PostStartHookContext) error { go customResourceDefinitionController.Run(context.StopCh) go namingController.Run(context.StopCh) + go finalizingController.Run(5, context.StopCh) return nil }) diff --git a/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/customresource_discovery_controller.go b/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/customresource_discovery_controller.go index 39cdb9c6ee3..3ba89146b29 100644 --- a/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/customresource_discovery_controller.go +++ b/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/customresource_discovery_controller.go @@ -101,12 +101,18 @@ func (c *DiscoveryController) sync(version schema.GroupVersion) error { } foundVersion = true + verbs := metav1.Verbs([]string{"delete", "deletecollection", "get", "list", "patch", "create", "update", "watch"}) + // if we're terminating we don't allow some verbs + if apiextensions.IsCRDConditionTrue(crd, apiextensions.Terminating) { + verbs = metav1.Verbs([]string{"delete", "deletecollection", "get", "list", "watch"}) + } + apiResourcesForDiscovery = append(apiResourcesForDiscovery, metav1.APIResource{ Name: crd.Status.AcceptedNames.Plural, SingularName: crd.Status.AcceptedNames.Singular, Namespaced: crd.Spec.Scope == apiextensions.NamespaceScoped, Kind: crd.Status.AcceptedNames.Kind, - Verbs: metav1.Verbs([]string{"delete", "deletecollection", "get", "list", "patch", "create", "update", "watch"}), + Verbs: verbs, ShortNames: crd.Status.AcceptedNames.ShortNames, }) } diff --git a/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/customresource_handler.go b/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/customresource_handler.go index 81feb20fcb4..d8723e69126 100644 --- a/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/customresource_handler.go +++ b/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/customresource_handler.go @@ -153,6 +153,8 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { r.delegate.ServeHTTP(w, req) } + terminating := apiextensions.IsCRDConditionTrue(crd, apiextensions.Terminating) + crdInfo := r.getServingInfoFor(crd) storage := crdInfo.storage requestScope := crdInfo.requestScope @@ -174,14 +176,26 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { handler(w, req) return case "create": + if terminating { + http.Error(w, fmt.Sprintf("%v not allowed while CustomResourceDefinition is terminating", requestInfo.Verb), http.StatusMethodNotAllowed) + return + } handler := handlers.CreateResource(storage, requestScope, discovery.NewUnstructuredObjectTyper(nil), r.admission) handler(w, req) return case "update": + if terminating { + http.Error(w, fmt.Sprintf("%v not allowed while CustomResourceDefinition is terminating", requestInfo.Verb), http.StatusMethodNotAllowed) + return + } handler := handlers.UpdateResource(storage, requestScope, discovery.NewUnstructuredObjectTyper(nil), r.admission) handler(w, req) return case "patch": + if terminating { + http.Error(w, fmt.Sprintf("%v not allowed while CustomResourceDefinition is terminating", requestInfo.Verb), http.StatusMethodNotAllowed) + return + } handler := handlers.PatchResource(storage, requestScope, r.admission, unstructured.UnstructuredObjectConverter{}) handler(w, req) return @@ -190,6 +204,11 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { handler := handlers.DeleteResource(storage, allowsOptions, requestScope, r.admission) handler(w, req) return + case "deletecollection": + checkBody := true + handler := handlers.DeleteCollection(storage, checkBody, requestScope, r.admission) + handler(w, req) + return default: http.Error(w, fmt.Sprintf("unhandled verb %q", requestInfo.Verb), http.StatusMethodNotAllowed) diff --git a/staging/src/k8s.io/kube-apiextensions-server/pkg/controller/finalizer/BUILD b/staging/src/k8s.io/kube-apiextensions-server/pkg/controller/finalizer/BUILD new file mode 100644 index 00000000000..00b7e0ce951 --- /dev/null +++ b/staging/src/k8s.io/kube-apiextensions-server/pkg/controller/finalizer/BUILD @@ -0,0 +1,34 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = ["crd_finalizer.go"], + tags = ["automanaged"], + deps = [ + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/client-go/dynamic:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", + "//vendor/k8s.io/client-go/util/workqueue:go_default_library", + "//vendor/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions:go_default_library", + "//vendor/k8s.io/kube-apiextensions-server/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion:go_default_library", + "//vendor/k8s.io/kube-apiextensions-server/pkg/client/informers/internalversion/apiextensions/internalversion:go_default_library", + "//vendor/k8s.io/kube-apiextensions-server/pkg/client/listers/apiextensions/internalversion:go_default_library", + ], +) diff --git a/staging/src/k8s.io/kube-apiextensions-server/pkg/controller/finalizer/crd_finalizer.go b/staging/src/k8s.io/kube-apiextensions-server/pkg/controller/finalizer/crd_finalizer.go new file mode 100644 index 00000000000..2f6516d1db1 --- /dev/null +++ b/staging/src/k8s.io/kube-apiextensions-server/pkg/controller/finalizer/crd_finalizer.go @@ -0,0 +1,333 @@ +/* +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 finalizer + +import ( + "fmt" + "reflect" + "time" + + "github.com/golang/glog" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime/schema" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" + + "k8s.io/kube-apiextensions-server/pkg/apis/apiextensions" + client "k8s.io/kube-apiextensions-server/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion" + informers "k8s.io/kube-apiextensions-server/pkg/client/informers/internalversion/apiextensions/internalversion" + listers "k8s.io/kube-apiextensions-server/pkg/client/listers/apiextensions/internalversion" +) + +var cloner = conversion.NewCloner() + +// This controller finalizes the CRD by deleting all the CRs associated with it. +type CRDFinalizer struct { + crdClient client.CustomResourceDefinitionsGetter + // clientPool is a dynamic client used to delete the individual instances + clientPool dynamic.ClientPool + + crdLister listers.CustomResourceDefinitionLister + crdSynced cache.InformerSynced + + // To allow injection for testing. + syncFn func(key string) error + + queue workqueue.RateLimitingInterface +} + +func NewCRDFinalizer( + crdInformer informers.CustomResourceDefinitionInformer, + crdClient client.CustomResourceDefinitionsGetter, + clientPool dynamic.ClientPool, +) *CRDFinalizer { + c := &CRDFinalizer{ + crdClient: crdClient, + clientPool: clientPool, + crdLister: crdInformer.Lister(), + crdSynced: crdInformer.Informer().HasSynced, + queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "CustomResourceDefinition-CRDFinalizer"), + } + + crdInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: c.addCustomResourceDefinition, + UpdateFunc: c.updateCustomResourceDefinition, + }) + + c.syncFn = c.sync + + return c +} + +func (c *CRDFinalizer) sync(key string) error { + cachedCRD, err := c.crdLister.Get(key) + if apierrors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + + // no work to do + if cachedCRD.DeletionTimestamp.IsZero() || !apiextensions.CRDHasFinalizer(cachedCRD, apiextensions.CustomResourceCleanupFinalizer) { + return nil + } + + crd := &apiextensions.CustomResourceDefinition{} + if err := apiextensions.DeepCopy_apiextensions_CustomResourceDefinition(cachedCRD, crd, cloner); err != nil { + return err + } + + // update the status condition. This cleanup could take a while. + apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{ + Type: apiextensions.Terminating, + Status: apiextensions.ConditionTrue, + Reason: "InstanceDeletionInProgress", + Message: "CustomResource deletion is in progress", + }) + crd, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd) + if err != nil { + return err + } + + // Its possible for a naming conflict to have removed this resource from the API after instances were created. + // For now we will cowardly stop finalizing. If we don't go through the REST API, weird things may happen: + // no audit trail, no admission checks or side effects, finalization would probably still work but defaulting + // would be missed. It would be a mess. + // This requires human intervention to solve, update status so they have a reason. + // TODO split coreNamesAccepted from extendedNamesAccepted. If coreNames were accepted, then we have something to cleanup + // and the endpoint is serviceable. if they aren't, then there's nothing to cleanup. + if !apiextensions.IsCRDConditionFalse(crd, apiextensions.NameConflict) { + apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{ + Type: apiextensions.Terminating, + Status: apiextensions.ConditionTrue, + Reason: "InstanceDeletionStuck", + Message: fmt.Sprintf("cannot proceed with deletion because of %v condition", apiextensions.NameConflict), + }) + crd, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd) + if err != nil { + return err + } + return fmt.Errorf("cannot proceed with deletion because of %v condition", apiextensions.NameConflict) + } + + // Now we can start deleting items. We should use the REST API to ensure that all normal admission runs. + // Since we control the endpoints, we know that delete collection works. + crClient, err := c.clientPool.ClientForGroupVersionResource(schema.GroupVersionResource{Group: crd.Spec.Group, Version: crd.Spec.Version, Resource: crd.Status.AcceptedNames.Plural}) + if err != nil { + return err + } + crAPIResource := &metav1.APIResource{ + Name: crd.Status.AcceptedNames.Plural, + SingularName: crd.Status.AcceptedNames.Singular, + Namespaced: crd.Spec.Scope == apiextensions.NamespaceScoped, + Kind: crd.Status.AcceptedNames.Kind, + Verbs: metav1.Verbs([]string{"deletecollection", "list"}), + ShortNames: crd.Status.AcceptedNames.ShortNames, + } + crResourceClient := crClient.Resource(crAPIResource, "" /* namespace all */) + allResources, err := crResourceClient.List(metav1.ListOptions{}) + if err != nil { + return err + } + + deletedNamespaces := sets.String{} + deleteErrors := []error{} + for _, item := range allResources.(*unstructured.UnstructuredList).Items { + metadata, err := meta.Accessor(&item) + if err != nil { + utilruntime.HandleError(err) + continue + } + if deletedNamespaces.Has(metadata.GetNamespace()) { + continue + } + // don't retry deleting the same namespace + deletedNamespaces.Insert(metadata.GetNamespace()) + if err := crClient.Resource(crAPIResource, metadata.GetNamespace()).DeleteCollection(nil, metav1.ListOptions{}); err != nil { + deleteErrors = append(deleteErrors, err) + continue + } + } + if deleteError := utilerrors.NewAggregate(deleteErrors); deleteError != nil { + apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{ + Type: apiextensions.Terminating, + Status: apiextensions.ConditionTrue, + Reason: "InstanceDeletionFailed", + Message: fmt.Sprintf("could not issue all deletes: %v", deleteError), + }) + crd, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd) + if err != nil { + utilruntime.HandleError(err) + } + return deleteError + } + + // now we need to wait until all the resources are deleted. Start with a simple poll before we do anything fancy. + // TODO not all servers are synchronized on caches. It is possible for a stale one to still be creating things. + // Once we have a mechanism for servers to indicate their states, we should check that for concurrence. + listErr := wait.PollImmediate(5*time.Second, 1*time.Minute, func() (bool, error) { + listObj, err := crResourceClient.List(metav1.ListOptions{}) + if err != nil { + return false, err + } + if len(listObj.(*unstructured.UnstructuredList).Items) == 0 { + return true, nil + } + glog.V(2).Infof("%s.%s waiting for %d items to be removed", crd.Status.AcceptedNames.Plural, crd.Spec.Group, len(listObj.(*unstructured.UnstructuredList).Items)) + return false, nil + }) + if listErr != nil { + apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{ + Type: apiextensions.Terminating, + Status: apiextensions.ConditionTrue, + Reason: "InstanceDeletionCheck", + Message: fmt.Sprintf("could not confirm zero CustomResources remaining: %v", listErr), + }) + crd, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd) + if err != nil { + utilruntime.HandleError(err) + } + return listErr + } + + apiextensions.SetCRDCondition(crd, apiextensions.CustomResourceDefinitionCondition{ + Type: apiextensions.Terminating, + Status: apiextensions.ConditionFalse, + Reason: "InstanceDeletionCompleted", + Message: "removed all instances", + }) + apiextensions.CRDRemoveFinalizer(crd, apiextensions.CustomResourceCleanupFinalizer) + crd, err = c.crdClient.CustomResourceDefinitions().UpdateStatus(crd) + if err != nil { + return err + } + + // and now issue another delete, which should clean it all up if no finalizers remain or no-op if they do + return c.crdClient.CustomResourceDefinitions().Delete(crd.Name, nil) +} + +func (c *CRDFinalizer) Run(workers int, stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + defer c.queue.ShutDown() + + glog.Infof("Starting CRDFinalizer") + defer glog.Infof("Shutting down CRDFinalizer") + + if !cache.WaitForCacheSync(stopCh, c.crdSynced) { + return + } + + for i := 0; i < workers; i++ { + go wait.Until(c.runWorker, time.Second, stopCh) + } + + <-stopCh +} + +func (c *CRDFinalizer) runWorker() { + for c.processNextWorkItem() { + } +} + +// processNextWorkItem deals with one key off the queue. It returns false when it's time to quit. +func (c *CRDFinalizer) processNextWorkItem() bool { + key, quit := c.queue.Get() + if quit { + return false + } + defer c.queue.Done(key) + + err := c.syncFn(key.(string)) + if err == nil { + c.queue.Forget(key) + return true + } + + utilruntime.HandleError(fmt.Errorf("%v failed with: %v", key, err)) + c.queue.AddRateLimited(key) + + return true +} + +func (c *CRDFinalizer) enqueue(obj *apiextensions.CustomResourceDefinition) { + key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) + if err != nil { + utilruntime.HandleError(fmt.Errorf("Couldn't get key for object %#v: %v", obj, err)) + return + } + + c.queue.Add(key) +} + +func (c *CRDFinalizer) addCustomResourceDefinition(obj interface{}) { + castObj := obj.(*apiextensions.CustomResourceDefinition) + // only queue deleted things + if !castObj.DeletionTimestamp.IsZero() && apiextensions.CRDHasFinalizer(castObj, apiextensions.CustomResourceCleanupFinalizer) { + c.enqueue(castObj) + } +} + +func (c *CRDFinalizer) updateCustomResourceDefinition(oldObj, newObj interface{}) { + oldCRD := oldObj.(*apiextensions.CustomResourceDefinition) + newCRD := newObj.(*apiextensions.CustomResourceDefinition) + // only queue deleted things that haven't been finalized by us + if newCRD.DeletionTimestamp.IsZero() || !apiextensions.CRDHasFinalizer(newCRD, apiextensions.CustomResourceCleanupFinalizer) { + return + } + + // always requeue resyncs just in case + if oldCRD.ResourceVersion == newCRD.ResourceVersion { + c.enqueue(newCRD) + return + } + + // If the only difference is in the terminating condition, then there's no reason to requeue here. This controller + // is likely to be the originator, so requeuing would hot-loop us. Failures are requeued by the workqueue directly. + // This is a low traffic and scale resource, so the copy is terrible. It's not good, so better ideas + // are welcome. + oldCopy := &apiextensions.CustomResourceDefinition{} + if err := apiextensions.DeepCopy_apiextensions_CustomResourceDefinition(oldCRD, oldCopy, cloner); err != nil { + utilruntime.HandleError(err) + c.enqueue(newCRD) + return + } + newCopy := &apiextensions.CustomResourceDefinition{} + if err := apiextensions.DeepCopy_apiextensions_CustomResourceDefinition(newCRD, newCopy, cloner); err != nil { + utilruntime.HandleError(err) + c.enqueue(newCRD) + return + } + oldCopy.ResourceVersion = "" + newCopy.ResourceVersion = "" + apiextensions.RemoveCRDCondition(oldCopy, apiextensions.Terminating) + apiextensions.RemoveCRDCondition(newCopy, apiextensions.Terminating) + + if !reflect.DeepEqual(oldCopy, newCopy) { + c.enqueue(newCRD) + } +} diff --git a/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresourcedefinition/BUILD b/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresourcedefinition/BUILD index ade49d817d3..59f4a682cc5 100644 --- a/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresourcedefinition/BUILD +++ b/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresourcedefinition/BUILD @@ -15,6 +15,8 @@ go_library( ], tags = ["automanaged"], deps = [ + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", @@ -24,6 +26,7 @@ go_library( "//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage:go_default_library", + "//vendor/k8s.io/apiserver/pkg/storage/errors:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library", "//vendor/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions:go_default_library", "//vendor/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/validation:go_default_library", diff --git a/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresourcedefinition/etcd.go b/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresourcedefinition/etcd.go index d19bffd1842..557a0494a46 100644 --- a/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresourcedefinition/etcd.go +++ b/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresourcedefinition/etcd.go @@ -17,11 +17,17 @@ limitations under the License. package customresourcedefinition import ( + "fmt" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" + "k8s.io/apiserver/pkg/storage" + storageerr "k8s.io/apiserver/pkg/storage/errors" "k8s.io/kube-apiextensions-server/pkg/apis/apiextensions" ) @@ -55,6 +61,87 @@ func NewREST(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) *REST return &REST{store} } +// Delete adds the CRD finalizer to the list +func (r *REST) Delete(ctx genericapirequest.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error) { + obj, err := r.Get(ctx, name, &metav1.GetOptions{}) + if err != nil { + return nil, false, err + } + + crd := obj.(*apiextensions.CustomResourceDefinition) + + // Ensure we have a UID precondition + if options == nil { + options = metav1.NewDeleteOptions(0) + } + if options.Preconditions == nil { + options.Preconditions = &metav1.Preconditions{} + } + if options.Preconditions.UID == nil { + options.Preconditions.UID = &crd.UID + } else if *options.Preconditions.UID != crd.UID { + err = apierrors.NewConflict( + apiextensions.Resource("customresourcedefinitions"), + name, + fmt.Errorf("Precondition failed: UID in precondition: %v, UID in object meta: %v", *options.Preconditions.UID, crd.UID), + ) + return nil, false, err + } + + // upon first request to delete, add our finalizer and then delegate + if crd.DeletionTimestamp.IsZero() { + key, err := r.Store.KeyFunc(ctx, name) + if err != nil { + return nil, false, err + } + + preconditions := storage.Preconditions{UID: options.Preconditions.UID} + + out := r.Store.NewFunc() + err = r.Store.Storage.GuaranteedUpdate( + ctx, key, out, false, &preconditions, + storage.SimpleUpdate(func(existing runtime.Object) (runtime.Object, error) { + existingCRD, ok := existing.(*apiextensions.CustomResourceDefinition) + if !ok { + // wrong type + return nil, fmt.Errorf("expected *apiextensions.CustomResourceDefinition, got %v", existing) + } + + // Set the deletion timestamp if needed + if existingCRD.DeletionTimestamp.IsZero() { + now := metav1.Now() + existingCRD.DeletionTimestamp = &now + } + + if !apiextensions.CRDHasFinalizer(existingCRD, apiextensions.CustomResourceCleanupFinalizer) { + existingCRD.Finalizers = append(existingCRD.Finalizers, apiextensions.CustomResourceCleanupFinalizer) + } + // update the status condition too + apiextensions.SetCRDCondition(existingCRD, apiextensions.CustomResourceDefinitionCondition{ + Type: apiextensions.Terminating, + Status: apiextensions.ConditionTrue, + Reason: "InstanceDeletionPending", + Message: "CustomResourceDefinition marked for deletion; CustomResource deletion will begin soon", + }) + return existingCRD, nil + }), + ) + + if err != nil { + err = storageerr.InterpretGetError(err, apiextensions.Resource("customresourcedefinitions"), name) + err = storageerr.InterpretUpdateError(err, apiextensions.Resource("customresourcedefinitions"), name) + if _, ok := err.(*apierrors.StatusError); !ok { + err = apierrors.NewInternalError(err) + } + return nil, false, err + } + + return out, false, nil + } + + return r.Store.Delete(ctx, name, options) +} + // NewStatusREST makes a RESTStorage for status that has more limited options. // It is based on the original REST so that we can share the same underlying store func NewStatusREST(scheme *runtime.Scheme, rest *REST) *StatusREST { diff --git a/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresourcedefinition/strategy.go b/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresourcedefinition/strategy.go index c29fe8144c2..07e77c5204d 100644 --- a/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresourcedefinition/strategy.go +++ b/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresourcedefinition/strategy.go @@ -89,7 +89,6 @@ func (statusStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old r newObj.Spec = oldObj.Spec newObj.Labels = oldObj.Labels newObj.Annotations = oldObj.Annotations - newObj.Finalizers = oldObj.Finalizers newObj.OwnerReferences = oldObj.OwnerReferences } diff --git a/staging/src/k8s.io/kube-apiextensions-server/test/integration/registration_test.go b/staging/src/k8s.io/kube-apiextensions-server/test/integration/registration_test.go index 22edc8f3be2..9505723e5d0 100644 --- a/staging/src/k8s.io/kube-apiextensions-server/test/integration/registration_test.go +++ b/staging/src/k8s.io/kube-apiextensions-server/test/integration/registration_test.go @@ -271,11 +271,6 @@ func TestDeRegistrationAndReRegistration(t *testing.T) { if _, err := instantiateCustomResource(t, testserver.NewNoxuInstance(ns, sameInstanceName), noxuNamespacedResourceClient, noxuDefinition); err != nil { t.Fatal(err) } - // Remove sameInstanceName since at the moment there's no finalizers. - // TODO: as soon finalizers will be implemented Delete can be removed. - if err := noxuNamespacedResourceClient.Delete(sameInstanceName, nil); err != nil { - t.Fatal(err) - } if err := testserver.DeleteCustomResourceDefinition(noxuDefinition, apiExtensionClient); err != nil { t.Fatal(err) } From d609f4ebca3882da1cc96846495828b936f47f7c Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Fri, 5 May 2017 02:14:38 -0400 Subject: [PATCH 10/23] Add pod util for extracting referenced configmaps --- pkg/api/pod/util.go | 61 ++++++++++++++++++++++++++++++++++++++++-- pkg/api/v1/pod/util.go | 7 +++-- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/pkg/api/pod/util.go b/pkg/api/pod/util.go index ec91c03276b..45e935b90aa 100644 --- a/pkg/api/pod/util.go +++ b/pkg/api/pod/util.go @@ -21,11 +21,14 @@ import ( "k8s.io/kubernetes/pkg/api" ) +// Visitor is called with each object name, and returns true if visiting should continue +type Visitor func(name string) (shouldContinue bool) + // VisitPodSecretNames invokes the visitor function with the name of every secret // referenced by the pod spec. If visitor returns false, visiting is short-circuited. // Transitive references (e.g. pod -> pvc -> pv -> secret) are not visited. // Returns true if visiting completed, false if visiting was short-circuited. -func VisitPodSecretNames(pod *api.Pod, visitor func(string) bool) bool { +func VisitPodSecretNames(pod *api.Pod, visitor Visitor) bool { for _, reference := range pod.Spec.ImagePullSecrets { if !visitor(reference.Name) { return false @@ -86,7 +89,7 @@ func VisitPodSecretNames(pod *api.Pod, visitor func(string) bool) bool { return true } -func visitContainerSecretNames(container *api.Container, visitor func(string) bool) bool { +func visitContainerSecretNames(container *api.Container, visitor Visitor) bool { for _, env := range container.EnvFrom { if env.SecretRef != nil { if !visitor(env.SecretRef.Name) { @@ -104,6 +107,60 @@ func visitContainerSecretNames(container *api.Container, visitor func(string) bo return true } +// VisitPodConfigmapNames invokes the visitor function with the name of every configmap +// referenced by the pod spec. If visitor returns false, visiting is short-circuited. +// Transitive references (e.g. pod -> pvc -> pv -> secret) are not visited. +// Returns true if visiting completed, false if visiting was short-circuited. +func VisitPodConfigmapNames(pod *api.Pod, visitor Visitor) bool { + for i := range pod.Spec.InitContainers { + if !visitContainerConfigmapNames(&pod.Spec.InitContainers[i], visitor) { + return false + } + } + for i := range pod.Spec.Containers { + if !visitContainerConfigmapNames(&pod.Spec.Containers[i], visitor) { + return false + } + } + var source *api.VolumeSource + for i := range pod.Spec.Volumes { + source = &pod.Spec.Volumes[i].VolumeSource + switch { + case source.Projected != nil: + for j := range source.Projected.Sources { + if source.Projected.Sources[j].ConfigMap != nil { + if !visitor(source.Projected.Sources[j].ConfigMap.Name) { + return false + } + } + } + case source.ConfigMap != nil: + if !visitor(source.ConfigMap.Name) { + return false + } + } + } + return true +} + +func visitContainerConfigmapNames(container *api.Container, visitor Visitor) bool { + for _, env := range container.EnvFrom { + if env.ConfigMapRef != nil { + if !visitor(env.ConfigMapRef.Name) { + return false + } + } + } + for _, envVar := range container.Env { + if envVar.ValueFrom != nil && envVar.ValueFrom.ConfigMapKeyRef != nil { + if !visitor(envVar.ValueFrom.ConfigMapKeyRef.Name) { + return false + } + } + } + return true +} + // IsPodReady returns true if a pod is ready; false otherwise. func IsPodReady(pod *api.Pod) bool { return IsPodReadyConditionTrue(pod.Status) diff --git a/pkg/api/v1/pod/util.go b/pkg/api/v1/pod/util.go index bb24a3c7626..85eb5338337 100644 --- a/pkg/api/v1/pod/util.go +++ b/pkg/api/v1/pod/util.go @@ -107,11 +107,14 @@ func SetInitContainersStatusesAnnotations(pod *v1.Pod) error { return nil } +// Visitor is called with each object name, and returns true if visiting should continue +type Visitor func(name string) (shouldContinue bool) + // VisitPodSecretNames invokes the visitor function with the name of every secret // referenced by the pod spec. If visitor returns false, visiting is short-circuited. // Transitive references (e.g. pod -> pvc -> pv -> secret) are not visited. // Returns true if visiting completed, false if visiting was short-circuited. -func VisitPodSecretNames(pod *v1.Pod, visitor func(string) bool) bool { +func VisitPodSecretNames(pod *v1.Pod, visitor Visitor) bool { for _, reference := range pod.Spec.ImagePullSecrets { if !visitor(reference.Name) { return false @@ -173,7 +176,7 @@ func VisitPodSecretNames(pod *v1.Pod, visitor func(string) bool) bool { return true } -func visitContainerSecretNames(container *v1.Container, visitor func(string) bool) bool { +func visitContainerSecretNames(container *v1.Container, visitor Visitor) bool { for _, env := range container.EnvFrom { if env.SecretRef != nil { if !visitor(env.SecretRef.Name) { From 3f8d6e921065ea7e11922b4db3d3542740b40ef7 Mon Sep 17 00:00:00 2001 From: p0lyn0mial Date: Thu, 18 May 2017 22:25:01 +0200 Subject: [PATCH 11/23] Change all the existing admission init blocks to call a Register function this is a two stage refactor when done there will be no init block in admission plugins. Instead all plugins expose Register function which accept admission.Plugins instance. The registration to global plugin registry happens inside Register func. --- plugin/pkg/admission/admit/admission.go | 7 ++++++- plugin/pkg/admission/alwayspullimages/admission.go | 7 ++++++- plugin/pkg/admission/antiaffinity/admission.go | 7 ++++++- .../pkg/admission/defaulttolerationseconds/admission.go | 7 ++++++- plugin/pkg/admission/deny/admission.go | 7 ++++++- plugin/pkg/admission/exec/admission.go | 9 +++++++-- plugin/pkg/admission/gc/gc_admission.go | 7 ++++++- plugin/pkg/admission/imagepolicy/admission.go | 7 ++++++- plugin/pkg/admission/initialresources/admission.go | 7 ++++++- plugin/pkg/admission/limitranger/admission.go | 7 ++++++- .../pkg/admission/namespace/autoprovision/admission.go | 7 ++++++- plugin/pkg/admission/namespace/exists/admission.go | 7 ++++++- plugin/pkg/admission/namespace/lifecycle/admission.go | 7 ++++++- plugin/pkg/admission/persistentvolume/label/admission.go | 7 ++++++- plugin/pkg/admission/podnodeselector/admission.go | 7 ++++++- plugin/pkg/admission/podpreset/admission.go | 7 ++++++- .../pkg/admission/podtolerationrestriction/admission.go | 5 +++++ plugin/pkg/admission/resourcequota/admission.go | 7 ++++++- .../admission/security/podsecuritypolicy/admission.go | 7 ++++++- plugin/pkg/admission/securitycontext/scdeny/admission.go | 7 ++++++- plugin/pkg/admission/serviceaccount/admission.go | 7 ++++++- plugin/pkg/admission/storageclass/default/admission.go | 7 ++++++- 22 files changed, 132 insertions(+), 22 deletions(-) diff --git a/plugin/pkg/admission/admit/admission.go b/plugin/pkg/admission/admit/admission.go index 2fce351fd45..7899e2d32fa 100644 --- a/plugin/pkg/admission/admit/admission.go +++ b/plugin/pkg/admission/admit/admission.go @@ -24,7 +24,12 @@ import ( ) func init() { - kubeapiserveradmission.Plugins.Register("AlwaysAdmit", func(config io.Reader) (admission.Interface, error) { + Register(&kubeapiserveradmission.Plugins) +} + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register("AlwaysAdmit", func(config io.Reader) (admission.Interface, error) { return NewAlwaysAdmit(), nil }) } diff --git a/plugin/pkg/admission/alwayspullimages/admission.go b/plugin/pkg/admission/alwayspullimages/admission.go index 437d71ea2ba..8b0172eaa28 100644 --- a/plugin/pkg/admission/alwayspullimages/admission.go +++ b/plugin/pkg/admission/alwayspullimages/admission.go @@ -34,7 +34,12 @@ import ( ) func init() { - kubeapiserveradmission.Plugins.Register("AlwaysPullImages", func(config io.Reader) (admission.Interface, error) { + Register(&kubeapiserveradmission.Plugins) +} + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register("AlwaysPullImages", func(config io.Reader) (admission.Interface, error) { return NewAlwaysPullImages(), nil }) } diff --git a/plugin/pkg/admission/antiaffinity/admission.go b/plugin/pkg/admission/antiaffinity/admission.go index 4ec64ef15fb..8aaf89a307f 100644 --- a/plugin/pkg/admission/antiaffinity/admission.go +++ b/plugin/pkg/admission/antiaffinity/admission.go @@ -28,7 +28,12 @@ import ( ) func init() { - kubeapiserveradmission.Plugins.Register("LimitPodHardAntiAffinityTopology", func(config io.Reader) (admission.Interface, error) { + Register(&kubeapiserveradmission.Plugins) +} + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register("LimitPodHardAntiAffinityTopology", func(config io.Reader) (admission.Interface, error) { return NewInterPodAntiAffinity(), nil }) } diff --git a/plugin/pkg/admission/defaulttolerationseconds/admission.go b/plugin/pkg/admission/defaulttolerationseconds/admission.go index a0c9c16e2d1..cbd0c650efc 100644 --- a/plugin/pkg/admission/defaulttolerationseconds/admission.go +++ b/plugin/pkg/admission/defaulttolerationseconds/admission.go @@ -40,7 +40,12 @@ var ( ) func init() { - kubeapiserveradmission.Plugins.Register("DefaultTolerationSeconds", func(config io.Reader) (admission.Interface, error) { + Register(&kubeapiserveradmission.Plugins) +} + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register("DefaultTolerationSeconds", func(config io.Reader) (admission.Interface, error) { return NewDefaultTolerationSeconds(), nil }) } diff --git a/plugin/pkg/admission/deny/admission.go b/plugin/pkg/admission/deny/admission.go index df8c0409116..0c127cfb6f8 100644 --- a/plugin/pkg/admission/deny/admission.go +++ b/plugin/pkg/admission/deny/admission.go @@ -25,7 +25,12 @@ import ( ) func init() { - kubeapiserveradmission.Plugins.Register("AlwaysDeny", func(config io.Reader) (admission.Interface, error) { + Register(&kubeapiserveradmission.Plugins) +} + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register("AlwaysDeny", func(config io.Reader) (admission.Interface, error) { return NewAlwaysDeny(), nil }) } diff --git a/plugin/pkg/admission/exec/admission.go b/plugin/pkg/admission/exec/admission.go index dcdb673b539..bcf7f1dfe1b 100644 --- a/plugin/pkg/admission/exec/admission.go +++ b/plugin/pkg/admission/exec/admission.go @@ -30,13 +30,18 @@ import ( ) func init() { - kubeapiserveradmission.Plugins.Register("DenyEscalatingExec", func(config io.Reader) (admission.Interface, error) { + Register(&kubeapiserveradmission.Plugins) +} + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register("DenyEscalatingExec", func(config io.Reader) (admission.Interface, error) { return NewDenyEscalatingExec(), nil }) // This is for legacy support of the DenyExecOnPrivileged admission controller. Most // of the time DenyEscalatingExec should be preferred. - kubeapiserveradmission.Plugins.Register("DenyExecOnPrivileged", func(config io.Reader) (admission.Interface, error) { + plugins.Register("DenyExecOnPrivileged", func(config io.Reader) (admission.Interface, error) { return NewDenyExecOnPrivileged(), nil }) } diff --git a/plugin/pkg/admission/gc/gc_admission.go b/plugin/pkg/admission/gc/gc_admission.go index 9450eb4fe9c..1f5df17dec9 100644 --- a/plugin/pkg/admission/gc/gc_admission.go +++ b/plugin/pkg/admission/gc/gc_admission.go @@ -32,7 +32,12 @@ import ( ) func init() { - kubeapiserveradmission.Plugins.Register("OwnerReferencesPermissionEnforcement", func(config io.Reader) (admission.Interface, error) { + Register(&kubeapiserveradmission.Plugins) +} + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register("OwnerReferencesPermissionEnforcement", func(config io.Reader) (admission.Interface, error) { // the pods/status endpoint is ignored by this plugin since old kubelets // corrupt them. the pod status strategy ensures status updates cannot mutate // ownerRef. diff --git a/plugin/pkg/admission/imagepolicy/admission.go b/plugin/pkg/admission/imagepolicy/admission.go index cc3819b7663..1edfe576d80 100644 --- a/plugin/pkg/admission/imagepolicy/admission.go +++ b/plugin/pkg/admission/imagepolicy/admission.go @@ -50,7 +50,12 @@ var ( ) func init() { - kubeapiserveradmission.Plugins.Register("ImagePolicyWebhook", func(config io.Reader) (admission.Interface, error) { + Register(&kubeapiserveradmission.Plugins) +} + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register("ImagePolicyWebhook", func(config io.Reader) (admission.Interface, error) { newImagePolicyWebhook, err := NewImagePolicyWebhook(config) if err != nil { return nil, err diff --git a/plugin/pkg/admission/initialresources/admission.go b/plugin/pkg/admission/initialresources/admission.go index 5acef40f4b8..401bb6d20f2 100644 --- a/plugin/pkg/admission/initialresources/admission.go +++ b/plugin/pkg/admission/initialresources/admission.go @@ -47,7 +47,12 @@ const ( // WARNING: this feature is experimental and will definitely change. func init() { - kubeapiserveradmission.Plugins.Register("InitialResources", func(config io.Reader) (admission.Interface, error) { + Register(&kubeapiserveradmission.Plugins) +} + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register("InitialResources", func(config io.Reader) (admission.Interface, error) { // TODO: remove the usage of flags in favor of reading versioned configuration s, err := newDataSource(*source) if err != nil { diff --git a/plugin/pkg/admission/limitranger/admission.go b/plugin/pkg/admission/limitranger/admission.go index 7fcf5ea2aa5..4d16388792b 100644 --- a/plugin/pkg/admission/limitranger/admission.go +++ b/plugin/pkg/admission/limitranger/admission.go @@ -44,7 +44,12 @@ const ( ) func init() { - kubeapiserveradmission.Plugins.Register("LimitRanger", func(config io.Reader) (admission.Interface, error) { + Register(&kubeapiserveradmission.Plugins) +} + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register("LimitRanger", func(config io.Reader) (admission.Interface, error) { return NewLimitRanger(&DefaultLimitRangerActions{}) }) } diff --git a/plugin/pkg/admission/namespace/autoprovision/admission.go b/plugin/pkg/admission/namespace/autoprovision/admission.go index d4054f42ac2..1de5ef6f070 100644 --- a/plugin/pkg/admission/namespace/autoprovision/admission.go +++ b/plugin/pkg/admission/namespace/autoprovision/admission.go @@ -31,7 +31,12 @@ import ( ) func init() { - kubeapiserveradmission.Plugins.Register("NamespaceAutoProvision", func(config io.Reader) (admission.Interface, error) { + Register(&kubeapiserveradmission.Plugins) +} + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register("NamespaceAutoProvision", func(config io.Reader) (admission.Interface, error) { return NewProvision(), nil }) } diff --git a/plugin/pkg/admission/namespace/exists/admission.go b/plugin/pkg/admission/namespace/exists/admission.go index 6b8e348a3a1..81ae1fad32c 100644 --- a/plugin/pkg/admission/namespace/exists/admission.go +++ b/plugin/pkg/admission/namespace/exists/admission.go @@ -31,7 +31,12 @@ import ( ) func init() { - kubeapiserveradmission.Plugins.Register("NamespaceExists", func(config io.Reader) (admission.Interface, error) { + Register(&kubeapiserveradmission.Plugins) +} + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register("NamespaceExists", func(config io.Reader) (admission.Interface, error) { return NewExists(), nil }) } diff --git a/plugin/pkg/admission/namespace/lifecycle/admission.go b/plugin/pkg/admission/namespace/lifecycle/admission.go index bb147584476..65ab0ef1cb0 100644 --- a/plugin/pkg/admission/namespace/lifecycle/admission.go +++ b/plugin/pkg/admission/namespace/lifecycle/admission.go @@ -51,7 +51,12 @@ const ( ) func init() { - kubeapiserveradmission.Plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { + Register(&kubeapiserveradmission.Plugins) +} + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { return NewLifecycle(sets.NewString(metav1.NamespaceDefault, metav1.NamespaceSystem, metav1.NamespacePublic)) }) } diff --git a/plugin/pkg/admission/persistentvolume/label/admission.go b/plugin/pkg/admission/persistentvolume/label/admission.go index 5bb711d1de7..50ad3018dbc 100644 --- a/plugin/pkg/admission/persistentvolume/label/admission.go +++ b/plugin/pkg/admission/persistentvolume/label/admission.go @@ -33,7 +33,12 @@ import ( ) func init() { - kubeapiserveradmission.Plugins.Register("PersistentVolumeLabel", func(config io.Reader) (admission.Interface, error) { + Register(&kubeapiserveradmission.Plugins) +} + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register("PersistentVolumeLabel", func(config io.Reader) (admission.Interface, error) { persistentVolumeLabelAdmission := NewPersistentVolumeLabel() return persistentVolumeLabelAdmission, nil }) diff --git a/plugin/pkg/admission/podnodeselector/admission.go b/plugin/pkg/admission/podnodeselector/admission.go index fdcda7eda80..2def9ed1eea 100644 --- a/plugin/pkg/admission/podnodeselector/admission.go +++ b/plugin/pkg/admission/podnodeselector/admission.go @@ -40,7 +40,12 @@ import ( var NamespaceNodeSelectors = []string{"scheduler.alpha.kubernetes.io/node-selector"} func init() { - kubeapiserveradmission.Plugins.Register("PodNodeSelector", func(config io.Reader) (admission.Interface, error) { + Register(&kubeapiserveradmission.Plugins) +} + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register("PodNodeSelector", func(config io.Reader) (admission.Interface, error) { // TODO move this to a versioned configuration file format. pluginConfig := readConfig(config) plugin := NewPodNodeSelector(pluginConfig.PodNodeSelectorPluginConfig) diff --git a/plugin/pkg/admission/podpreset/admission.go b/plugin/pkg/admission/podpreset/admission.go index 5948de35866..416c5221b01 100644 --- a/plugin/pkg/admission/podpreset/admission.go +++ b/plugin/pkg/admission/podpreset/admission.go @@ -42,7 +42,12 @@ const ( ) func init() { - kubeapiserveradmission.Plugins.Register(pluginName, func(config io.Reader) (admission.Interface, error) { + Register(&kubeapiserveradmission.Plugins) +} + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register(pluginName, func(config io.Reader) (admission.Interface, error) { return NewPlugin(), nil }) } diff --git a/plugin/pkg/admission/podtolerationrestriction/admission.go b/plugin/pkg/admission/podtolerationrestriction/admission.go index 04497436858..6c2b5f0920d 100644 --- a/plugin/pkg/admission/podtolerationrestriction/admission.go +++ b/plugin/pkg/admission/podtolerationrestriction/admission.go @@ -37,6 +37,11 @@ import ( ) func init() { + Register(&kubeapiserveradmission.Plugins) +} + +// Register registers a plugin +func Register(plugins *admission.Plugins) { kubeapiserveradmission.Plugins.Register("PodTolerationRestriction", func(config io.Reader) (admission.Interface, error) { pluginConfig, err := loadConfiguration(config) if err != nil { diff --git a/plugin/pkg/admission/resourcequota/admission.go b/plugin/pkg/admission/resourcequota/admission.go index 949cde03b4d..0d9f976d287 100644 --- a/plugin/pkg/admission/resourcequota/admission.go +++ b/plugin/pkg/admission/resourcequota/admission.go @@ -33,7 +33,12 @@ import ( ) func init() { - kubeapiserveradmission.Plugins.Register("ResourceQuota", + Register(&kubeapiserveradmission.Plugins) +} + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register("ResourceQuota", func(config io.Reader) (admission.Interface, error) { // load the configuration provided (if any) configuration, err := LoadConfiguration(config) diff --git a/plugin/pkg/admission/security/podsecuritypolicy/admission.go b/plugin/pkg/admission/security/podsecuritypolicy/admission.go index 012e474aa85..d1795a3326f 100644 --- a/plugin/pkg/admission/security/podsecuritypolicy/admission.go +++ b/plugin/pkg/admission/security/podsecuritypolicy/admission.go @@ -45,7 +45,12 @@ const ( ) func init() { - kubeapiserveradmission.Plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { + Register(&kubeapiserveradmission.Plugins) +} + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { plugin := NewPlugin(psp.NewSimpleStrategyFactory(), getMatchingPolicies, true) return plugin, nil }) diff --git a/plugin/pkg/admission/securitycontext/scdeny/admission.go b/plugin/pkg/admission/securitycontext/scdeny/admission.go index 443cefab681..9f709c3bb3a 100644 --- a/plugin/pkg/admission/securitycontext/scdeny/admission.go +++ b/plugin/pkg/admission/securitycontext/scdeny/admission.go @@ -27,7 +27,12 @@ import ( ) func init() { - kubeapiserveradmission.Plugins.Register("SecurityContextDeny", func(config io.Reader) (admission.Interface, error) { + Register(&kubeapiserveradmission.Plugins) +} + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register("SecurityContextDeny", func(config io.Reader) (admission.Interface, error) { return NewSecurityContextDeny(), nil }) } diff --git a/plugin/pkg/admission/serviceaccount/admission.go b/plugin/pkg/admission/serviceaccount/admission.go index 2c21f5d0105..17f35268725 100644 --- a/plugin/pkg/admission/serviceaccount/admission.go +++ b/plugin/pkg/admission/serviceaccount/admission.go @@ -54,7 +54,12 @@ const DefaultAPITokenMountPath = "/var/run/secrets/kubernetes.io/serviceaccount" const PluginName = "ServiceAccount" func init() { - kubeapiserveradmission.Plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { + Register(&kubeapiserveradmission.Plugins) +} + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { serviceAccountAdmission := NewServiceAccount() return serviceAccountAdmission, nil }) diff --git a/plugin/pkg/admission/storageclass/default/admission.go b/plugin/pkg/admission/storageclass/default/admission.go index 568961badaa..d22e13ed5b8 100644 --- a/plugin/pkg/admission/storageclass/default/admission.go +++ b/plugin/pkg/admission/storageclass/default/admission.go @@ -39,7 +39,12 @@ const ( ) func init() { - kubeapiserveradmission.Plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { + Register(&kubeapiserveradmission.Plugins) +} + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { plugin := newPlugin() return plugin, nil }) From 0c516c3ac216cca8be8b033a712069d4d0336ec0 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Tue, 16 May 2017 16:09:00 -0400 Subject: [PATCH 12/23] Add NodeIdentifier interface and default implementation --- hack/.linted_packages | 1 + pkg/BUILD | 1 + pkg/auth/nodeidentifier/BUILD | 40 +++++++++++++++ pkg/auth/nodeidentifier/default.go | 64 +++++++++++++++++++++++ pkg/auth/nodeidentifier/default_test.go | 68 +++++++++++++++++++++++++ pkg/auth/nodeidentifier/interfaces.go | 30 +++++++++++ 6 files changed, 204 insertions(+) create mode 100644 pkg/auth/nodeidentifier/BUILD create mode 100644 pkg/auth/nodeidentifier/default.go create mode 100644 pkg/auth/nodeidentifier/default_test.go create mode 100644 pkg/auth/nodeidentifier/interfaces.go diff --git a/hack/.linted_packages b/hack/.linted_packages index f3ef847cdf6..71fc2db171b 100644 --- a/hack/.linted_packages +++ b/hack/.linted_packages @@ -86,6 +86,7 @@ pkg/apis/settings/install pkg/apis/settings/validation pkg/apis/storage/install pkg/apis/storage/validation +pkg/auth/nodeidentifier pkg/bootstrap/api pkg/client/conditions pkg/client/informers/informers_generated/externalversions diff --git a/pkg/BUILD b/pkg/BUILD index 83cd2a523df..2f5f692342c 100644 --- a/pkg/BUILD +++ b/pkg/BUILD @@ -31,6 +31,7 @@ filegroup( "//pkg/apis/settings:all-srcs", "//pkg/apis/storage:all-srcs", "//pkg/auth/authorizer/abac:all-srcs", + "//pkg/auth/nodeidentifier:all-srcs", "//pkg/auth/user:all-srcs", "//pkg/bootstrap/api:all-srcs", "//pkg/capabilities:all-srcs", diff --git a/pkg/auth/nodeidentifier/BUILD b/pkg/auth/nodeidentifier/BUILD new file mode 100644 index 00000000000..cc228ad79c1 --- /dev/null +++ b/pkg/auth/nodeidentifier/BUILD @@ -0,0 +1,40 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = [ + "default.go", + "interfaces.go", + ], + tags = ["automanaged"], + deps = ["//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library"], +) + +go_test( + name = "go_default_test", + srcs = ["default_test.go"], + library = ":go_default_library", + tags = ["automanaged"], + deps = ["//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/pkg/auth/nodeidentifier/default.go b/pkg/auth/nodeidentifier/default.go new file mode 100644 index 00000000000..80df38ba4f3 --- /dev/null +++ b/pkg/auth/nodeidentifier/default.go @@ -0,0 +1,64 @@ +/* +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 nodeidentifier + +import ( + "strings" + + "k8s.io/apiserver/pkg/authentication/user" +) + +// NewDefaultNodeIdentifier returns a default NodeIdentifier implementation, +// which returns isNode=true if the user groups contain the system:nodes group, +// and populates nodeName if isNode is true, and the user name is in the format system:node: +func NewDefaultNodeIdentifier() NodeIdentifier { + return defaultNodeIdentifier{} +} + +// defaultNodeIdentifier implements NodeIdentifier +type defaultNodeIdentifier struct{} + +// nodeUserNamePrefix is the prefix for usernames in the form `system:node:` +const nodeUserNamePrefix = "system:node:" + +// NodeIdentity returns isNode=true if the user groups contain the system:nodes group, +// and populates nodeName if isNode is true, and the user name is in the format system:node: +func (defaultNodeIdentifier) NodeIdentity(u user.Info) (string, bool) { + // Make sure we're a node, and can parse the node name + if u == nil { + return "", false + } + + isNode := false + for _, g := range u.GetGroups() { + if g == user.NodesGroup { + isNode = true + break + } + } + if !isNode { + return "", false + } + + userName := u.GetName() + nodeName := "" + if strings.HasPrefix(userName, nodeUserNamePrefix) { + nodeName = strings.TrimPrefix(userName, nodeUserNamePrefix) + } + + return nodeName, isNode +} diff --git a/pkg/auth/nodeidentifier/default_test.go b/pkg/auth/nodeidentifier/default_test.go new file mode 100644 index 00000000000..fee38d57296 --- /dev/null +++ b/pkg/auth/nodeidentifier/default_test.go @@ -0,0 +1,68 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package nodeidentifier + +import ( + "testing" + + "k8s.io/apiserver/pkg/authentication/user" +) + +func TestDefaultNodeIdentifier_NodeIdentity(t *testing.T) { + tests := []struct { + name string + user user.Info + expectNodeName string + expectIsNode bool + }{ + { + name: "nil user", + user: nil, + expectNodeName: "", + expectIsNode: false, + }, + { + name: "node username without group", + user: &user.DefaultInfo{Name: "system:node:foo"}, + expectNodeName: "", + expectIsNode: false, + }, + { + name: "node group without username", + user: &user.DefaultInfo{Name: "foo", Groups: []string{"system:nodes"}}, + expectNodeName: "", + expectIsNode: true, + }, + { + name: "node group and username", + user: &user.DefaultInfo{Name: "system:node:foo", Groups: []string{"system:nodes"}}, + expectNodeName: "foo", + expectIsNode: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + nodeName, isNode := NewDefaultNodeIdentifier().NodeIdentity(tt.user) + if nodeName != tt.expectNodeName { + t.Errorf("DefaultNodeIdentifier.NodeIdentity() got = %v, want %v", nodeName, tt.expectNodeName) + } + if isNode != tt.expectIsNode { + t.Errorf("DefaultNodeIdentifier.NodeIdentity() got1 = %v, want %v", isNode, tt.expectIsNode) + } + }) + } +} diff --git a/pkg/auth/nodeidentifier/interfaces.go b/pkg/auth/nodeidentifier/interfaces.go new file mode 100644 index 00000000000..917bebaf9d9 --- /dev/null +++ b/pkg/auth/nodeidentifier/interfaces.go @@ -0,0 +1,30 @@ +/* +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 nodeidentifier + +import ( + "k8s.io/apiserver/pkg/authentication/user" +) + +// NodeIdentifier determines node information from a given user +type NodeIdentifier interface { + // IdentifyNode determines node information from the given user.Info. + // nodeName is the name of the Node API object associated with the user.Info, + // and may be empty if a specific node cannot be determined. + // isNode is true if the user.Info represents an identity issued to a node. + NodeIdentity(user.Info) (nodeName string, isNode bool) +} From 6fd36792f185a2f846d08ff5547460b25435b413 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Tue, 16 May 2017 16:09:28 -0400 Subject: [PATCH 13/23] Add NodeRestriction admission plugin --- cmd/kube-apiserver/app/BUILD | 1 + cmd/kube-apiserver/app/plugins.go | 1 + hack/local-up-cluster.sh | 3 + plugin/BUILD | 1 + plugin/pkg/admission/noderestriction/BUILD | 54 ++ plugin/pkg/admission/noderestriction/OWNERS | 8 + .../admission/noderestriction/admission.go | 203 ++++++++ .../noderestriction/admission_test.go | 476 ++++++++++++++++++ 8 files changed, 747 insertions(+) create mode 100644 plugin/pkg/admission/noderestriction/BUILD create mode 100644 plugin/pkg/admission/noderestriction/OWNERS create mode 100644 plugin/pkg/admission/noderestriction/admission.go create mode 100644 plugin/pkg/admission/noderestriction/admission_test.go diff --git a/cmd/kube-apiserver/app/BUILD b/cmd/kube-apiserver/app/BUILD index b671a78ee65..d93e5b64aac 100644 --- a/cmd/kube-apiserver/app/BUILD +++ b/cmd/kube-apiserver/app/BUILD @@ -54,6 +54,7 @@ go_library( "//plugin/pkg/admission/namespace/autoprovision:go_default_library", "//plugin/pkg/admission/namespace/exists:go_default_library", "//plugin/pkg/admission/namespace/lifecycle:go_default_library", + "//plugin/pkg/admission/noderestriction:go_default_library", "//plugin/pkg/admission/persistentvolume/label:go_default_library", "//plugin/pkg/admission/podnodeselector:go_default_library", "//plugin/pkg/admission/podpreset:go_default_library", diff --git a/cmd/kube-apiserver/app/plugins.go b/cmd/kube-apiserver/app/plugins.go index f5d524ab6d8..0ddd92566ad 100644 --- a/cmd/kube-apiserver/app/plugins.go +++ b/cmd/kube-apiserver/app/plugins.go @@ -37,6 +37,7 @@ import ( _ "k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision" _ "k8s.io/kubernetes/plugin/pkg/admission/namespace/exists" _ "k8s.io/kubernetes/plugin/pkg/admission/namespace/lifecycle" + _ "k8s.io/kubernetes/plugin/pkg/admission/noderestriction" _ "k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label" _ "k8s.io/kubernetes/plugin/pkg/admission/podnodeselector" _ "k8s.io/kubernetes/plugin/pkg/admission/podpreset" diff --git a/hack/local-up-cluster.sh b/hack/local-up-cluster.sh index 61fed3bfff2..955cb3857e8 100755 --- a/hack/local-up-cluster.sh +++ b/hack/local-up-cluster.sh @@ -388,6 +388,9 @@ function start_apiserver { if [[ -n "${PSP_ADMISSION}" ]]; then security_admission=",PodSecurityPolicy" fi + if [[ -n "${NODE_ADMISSION}" ]]; then + security_admission=",NodeRestriction" + fi # Admission Controllers to invoke prior to persisting objects in cluster ADMISSION_CONTROL=NamespaceLifecycle,LimitRanger,ServiceAccount${security_admission},ResourceQuota,DefaultStorageClass,DefaultTolerationSeconds diff --git a/plugin/BUILD b/plugin/BUILD index cf8c9c5be76..9f7368ddf2b 100644 --- a/plugin/BUILD +++ b/plugin/BUILD @@ -27,6 +27,7 @@ filegroup( "//plugin/pkg/admission/namespace/autoprovision:all-srcs", "//plugin/pkg/admission/namespace/exists:all-srcs", "//plugin/pkg/admission/namespace/lifecycle:all-srcs", + "//plugin/pkg/admission/noderestriction:all-srcs", "//plugin/pkg/admission/persistentvolume/label:all-srcs", "//plugin/pkg/admission/podnodeselector:all-srcs", "//plugin/pkg/admission/podpreset:all-srcs", diff --git a/plugin/pkg/admission/noderestriction/BUILD b/plugin/pkg/admission/noderestriction/BUILD new file mode 100644 index 00000000000..83fcdd19b9f --- /dev/null +++ b/plugin/pkg/admission/noderestriction/BUILD @@ -0,0 +1,54 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = ["admission.go"], + tags = ["automanaged"], + deps = [ + "//pkg/api:go_default_library", + "//pkg/api/pod:go_default_library", + "//pkg/auth/nodeidentifier:go_default_library", + "//pkg/client/clientset_generated/internalclientset:go_default_library", + "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", + "//pkg/kubeapiserver/admission:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["admission_test.go"], + library = ":go_default_library", + tags = ["automanaged"], + deps = [ + "//pkg/api:go_default_library", + "//pkg/auth/nodeidentifier:go_default_library", + "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", + "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/plugin/pkg/admission/noderestriction/OWNERS b/plugin/pkg/admission/noderestriction/OWNERS new file mode 100644 index 00000000000..e58cadf54d4 --- /dev/null +++ b/plugin/pkg/admission/noderestriction/OWNERS @@ -0,0 +1,8 @@ +approvers: +- deads2k +- liggitt +- timstclair +reviewers: +- deads2k +- liggitt +- timstclair diff --git a/plugin/pkg/admission/noderestriction/admission.go b/plugin/pkg/admission/noderestriction/admission.go new file mode 100644 index 00000000000..7eabfdf7a73 --- /dev/null +++ b/plugin/pkg/admission/noderestriction/admission.go @@ -0,0 +1,203 @@ +/* +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 node + +import ( + "fmt" + "io" + + "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apiserver/pkg/admission" + "k8s.io/kubernetes/pkg/api" + podutil "k8s.io/kubernetes/pkg/api/pod" + "k8s.io/kubernetes/pkg/auth/nodeidentifier" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + coreinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" + kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission" +) + +const ( + PluginName = "NodeRestriction" +) + +func init() { + kubeapiserveradmission.Plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { + return NewPlugin(nodeidentifier.NewDefaultNodeIdentifier(), false), nil + }) +} + +// NewPlugin creates a new NodeRestriction admission plugin. +// This plugin identifies requests from nodes +func NewPlugin(nodeIdentifier nodeidentifier.NodeIdentifier, strict bool) *nodePlugin { + return &nodePlugin{ + Handler: admission.NewHandler(admission.Create, admission.Update, admission.Delete), + nodeIdentifier: nodeIdentifier, + strict: strict, + } +} + +// nodePlugin holds state for and implements the admission plugin. +type nodePlugin struct { + *admission.Handler + strict bool + nodeIdentifier nodeidentifier.NodeIdentifier + podsGetter coreinternalversion.PodsGetter +} + +var ( + _ = admission.Interface(&nodePlugin{}) + _ = kubeapiserveradmission.WantsInternalKubeClientSet(&nodePlugin{}) +) + +func (p *nodePlugin) SetInternalKubeClientSet(f internalclientset.Interface) { + p.podsGetter = f.Core() +} + +func (p *nodePlugin) Validate() error { + if p.nodeIdentifier == nil { + return fmt.Errorf("%s requires a node identifier", PluginName) + } + if p.podsGetter == nil { + return fmt.Errorf("%s requires a pod getter", PluginName) + } + return nil +} + +var ( + podResource = api.Resource("pods") + nodeResource = api.Resource("nodes") +) + +func (c *nodePlugin) Admit(a admission.Attributes) error { + nodeName, isNode := c.nodeIdentifier.NodeIdentity(a.GetUserInfo()) + + // Our job is just to restrict nodes + if !isNode { + return nil + } + + if len(nodeName) == 0 { + if c.strict { + // In strict mode, disallow requests from nodes we cannot match to a particular node + return admission.NewForbidden(a, fmt.Errorf("could not determine node identity from user")) + } + // Our job is just to restrict identifiable nodes + return nil + } + + switch a.GetResource().GroupResource() { + case podResource: + switch a.GetSubresource() { + case "": + return c.admitPod(nodeName, a) + case "status": + return c.admitPodStatus(nodeName, a) + default: + return admission.NewForbidden(a, fmt.Errorf("unexpected pod subresource %s", a.GetSubresource())) + } + + case nodeResource: + return c.admitNode(nodeName, a) + + default: + return nil + } +} + +func (c *nodePlugin) admitPod(nodeName string, a admission.Attributes) error { + switch a.GetOperation() { + case admission.Create: + // require a pod object + pod, ok := a.GetObject().(*api.Pod) + if !ok { + return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject())) + } + + // only allow nodes to create mirror pods + if _, isMirrorPod := pod.Annotations[api.MirrorPodAnnotationKey]; !isMirrorPod { + return admission.NewForbidden(a, fmt.Errorf("pod does not have %q annotation, node %s can only create mirror pods", api.MirrorPodAnnotationKey, nodeName)) + } + + // only allow nodes to create a pod bound to itself + if pod.Spec.NodeName != nodeName { + return admission.NewForbidden(a, fmt.Errorf("node %s can only create pods with spec.nodeName set to itself", nodeName)) + } + + // don't allow a node to create a pod that references any other API objects + if pod.Spec.ServiceAccountName != "" { + return admission.NewForbidden(a, fmt.Errorf("node %s can not create pods that reference a service account", nodeName)) + } + hasSecrets := false + podutil.VisitPodSecretNames(pod, func(name string) (shouldContinue bool) { hasSecrets = true; return false }) + if hasSecrets { + return admission.NewForbidden(a, fmt.Errorf("node %s can not create pods that reference secrets", nodeName)) + } + hasConfigMaps := false + podutil.VisitPodConfigmapNames(pod, func(name string) (shouldContinue bool) { hasConfigMaps = true; return false }) + if hasConfigMaps { + return admission.NewForbidden(a, fmt.Errorf("node %s can not create pods that reference configmaps", nodeName)) + } + for _, v := range pod.Spec.Volumes { + if v.PersistentVolumeClaim != nil { + return admission.NewForbidden(a, fmt.Errorf("node %s can not create pods that reference persistentvolumeclaims", nodeName)) + } + } + + return nil + + case admission.Delete: + // get the existing pod + existingPod, err := c.podsGetter.Pods(a.GetNamespace()).Get(a.GetName(), v1.GetOptions{ResourceVersion: "0"}) + if err != nil { + return admission.NewForbidden(a, err) + } + // only allow a node to delete a pod bound to itself + if existingPod.Spec.NodeName != nodeName { + return admission.NewForbidden(a, fmt.Errorf("node %s can only delete pods with spec.nodeName set to itself", nodeName)) + } + return nil + + default: + return admission.NewForbidden(a, fmt.Errorf("unexpected operation %s", a.GetOperation())) + } +} + +func (c *nodePlugin) admitPodStatus(nodeName string, a admission.Attributes) error { + switch a.GetOperation() { + case admission.Update: + // require an existing pod + pod, ok := a.GetOldObject().(*api.Pod) + if !ok { + return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject())) + } + // only allow a node to update status of a pod bound to itself + if pod.Spec.NodeName != nodeName { + return admission.NewForbidden(a, fmt.Errorf("node %s can only update pod status for pods with spec.nodeName set to itself", nodeName)) + } + return nil + + default: + return admission.NewForbidden(a, fmt.Errorf("unexpected operation %s", a.GetOperation())) + } +} + +func (c *nodePlugin) admitNode(nodeName string, a admission.Attributes) error { + if a.GetName() != nodeName { + return admission.NewForbidden(a, fmt.Errorf("cannot modify other nodes")) + } + return nil +} diff --git a/plugin/pkg/admission/noderestriction/admission_test.go b/plugin/pkg/admission/noderestriction/admission_test.go new file mode 100644 index 00000000000..5cb0eb23bb8 --- /dev/null +++ b/plugin/pkg/admission/noderestriction/admission_test.go @@ -0,0 +1,476 @@ +/* +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 node + +import ( + "strings" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/auth/nodeidentifier" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + coreinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" +) + +func makeTestPod(namespace, name, node string, mirror bool) *api.Pod { + pod := &api.Pod{} + pod.Namespace = namespace + pod.Name = name + pod.Spec.NodeName = node + if mirror { + pod.Annotations = map[string]string{api.MirrorPodAnnotationKey: "true"} + } + return pod +} + +func Test_nodePlugin_Admit(t *testing.T) { + var ( + mynode = &user.DefaultInfo{Name: "system:node:mynode", Groups: []string{"system:nodes"}} + bob = &user.DefaultInfo{Name: "bob"} + + mynodeObj = &api.Node{ObjectMeta: metav1.ObjectMeta{Name: "mynode"}} + othernodeObj = &api.Node{ObjectMeta: metav1.ObjectMeta{Name: "othernode"}} + + mymirrorpod = makeTestPod("ns", "mymirrorpod", "mynode", true) + othermirrorpod = makeTestPod("ns", "othermirrorpod", "othernode", true) + unboundmirrorpod = makeTestPod("ns", "unboundmirrorpod", "", true) + mypod = makeTestPod("ns", "mypod", "mynode", false) + otherpod = makeTestPod("ns", "otherpod", "othernode", false) + unboundpod = makeTestPod("ns", "unboundpod", "", false) + + configmapResource = api.Resource("configmap").WithVersion("v1") + configmapKind = api.Kind("ConfigMap").WithVersion("v1") + + podResource = api.Resource("pods").WithVersion("v1") + podKind = api.Kind("Pod").WithVersion("v1") + + nodeResource = api.Resource("nodes").WithVersion("v1") + nodeKind = api.Kind("Node").WithVersion("v1") + + noExistingPods = fake.NewSimpleClientset().Core() + existingPods = fake.NewSimpleClientset(mymirrorpod, othermirrorpod, unboundmirrorpod, mypod, otherpod, unboundpod).Core() + ) + + sapod := makeTestPod("ns", "mysapod", "mynode", true) + sapod.Spec.ServiceAccountName = "foo" + + secretpod := makeTestPod("ns", "mysecretpod", "mynode", true) + secretpod.Spec.Volumes = []api.Volume{{VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: "foo"}}}} + + configmappod := makeTestPod("ns", "myconfigmappod", "mynode", true) + configmappod.Spec.Volumes = []api.Volume{{VolumeSource: api.VolumeSource{ConfigMap: &api.ConfigMapVolumeSource{LocalObjectReference: api.LocalObjectReference{Name: "foo"}}}}} + + pvcpod := makeTestPod("ns", "mypvcpod", "mynode", true) + pvcpod.Spec.Volumes = []api.Volume{{VolumeSource: api.VolumeSource{PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{ClaimName: "foo"}}}} + + tests := []struct { + name string + strict bool + podsGetter coreinternalversion.PodsGetter + attributes admission.Attributes + err string + }{ + // Mirror pods bound to us + { + name: "allow creating a mirror pod bound to self", + podsGetter: noExistingPods, + attributes: admission.NewAttributesRecord(mymirrorpod, nil, podKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "", admission.Create, mynode), + err: "", + }, + { + name: "forbid update of mirror pod bound to self", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(mymirrorpod, mymirrorpod, podKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "", admission.Update, mynode), + err: "forbidden: unexpected operation", + }, + { + name: "allow delete of mirror pod bound to self", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(nil, nil, podKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "", admission.Delete, mynode), + err: "", + }, + { + name: "forbid create of mirror pod status bound to self", + podsGetter: noExistingPods, + attributes: admission.NewAttributesRecord(mymirrorpod, nil, podKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "status", admission.Create, mynode), + err: "forbidden: unexpected operation", + }, + { + name: "allow update of mirror pod status bound to self", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(mymirrorpod, mymirrorpod, podKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "status", admission.Update, mynode), + err: "", + }, + { + name: "forbid delete of mirror pod status bound to self", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(nil, nil, podKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "status", admission.Delete, mynode), + err: "forbidden: unexpected operation", + }, + + // Mirror pods bound to another node + { + name: "forbid creating a mirror pod bound to another", + podsGetter: noExistingPods, + attributes: admission.NewAttributesRecord(othermirrorpod, nil, podKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "", admission.Create, mynode), + err: "spec.nodeName set to itself", + }, + { + name: "forbid update of mirror pod bound to another", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(othermirrorpod, othermirrorpod, podKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "", admission.Update, mynode), + err: "forbidden: unexpected operation", + }, + { + name: "forbid delete of mirror pod bound to another", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(nil, nil, podKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "", admission.Delete, mynode), + err: "spec.nodeName set to itself", + }, + { + name: "forbid create of mirror pod status bound to another", + podsGetter: noExistingPods, + attributes: admission.NewAttributesRecord(othermirrorpod, nil, podKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "status", admission.Create, mynode), + err: "forbidden: unexpected operation", + }, + { + name: "forbid update of mirror pod status bound to another", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(othermirrorpod, othermirrorpod, podKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "status", admission.Update, mynode), + err: "spec.nodeName set to itself", + }, + { + name: "forbid delete of mirror pod status bound to another", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(nil, nil, podKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "status", admission.Delete, mynode), + err: "forbidden: unexpected operation", + }, + + // Mirror pods not bound to any node + { + name: "forbid creating a mirror pod unbound", + podsGetter: noExistingPods, + attributes: admission.NewAttributesRecord(unboundmirrorpod, nil, podKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "", admission.Create, mynode), + err: "spec.nodeName set to itself", + }, + { + name: "forbid update of mirror pod unbound", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(unboundmirrorpod, unboundmirrorpod, podKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "", admission.Update, mynode), + err: "forbidden: unexpected operation", + }, + { + name: "forbid delete of mirror pod unbound", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "", admission.Delete, mynode), + err: "spec.nodeName set to itself", + }, + { + name: "forbid create of mirror pod status unbound", + podsGetter: noExistingPods, + attributes: admission.NewAttributesRecord(unboundmirrorpod, nil, podKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "status", admission.Create, mynode), + err: "forbidden: unexpected operation", + }, + { + name: "forbid update of mirror pod status unbound", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(unboundmirrorpod, unboundmirrorpod, podKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "status", admission.Update, mynode), + err: "spec.nodeName set to itself", + }, + { + name: "forbid delete of mirror pod status unbound", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "status", admission.Delete, mynode), + err: "forbidden: unexpected operation", + }, + + // Normal pods bound to us + { + name: "forbid creating a normal pod bound to self", + podsGetter: noExistingPods, + attributes: admission.NewAttributesRecord(mypod, nil, podKind, mypod.Namespace, mypod.Name, podResource, "", admission.Create, mynode), + err: "can only create mirror pods", + }, + { + name: "forbid update of normal pod bound to self", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(mypod, mypod, podKind, mypod.Namespace, mypod.Name, podResource, "", admission.Update, mynode), + err: "forbidden: unexpected operation", + }, + { + name: "allow delete of normal pod bound to self", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(nil, nil, podKind, mypod.Namespace, mypod.Name, podResource, "", admission.Delete, mynode), + err: "", + }, + { + name: "forbid create of normal pod status bound to self", + podsGetter: noExistingPods, + attributes: admission.NewAttributesRecord(mypod, nil, podKind, mypod.Namespace, mypod.Name, podResource, "status", admission.Create, mynode), + err: "forbidden: unexpected operation", + }, + { + name: "allow update of normal pod status bound to self", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(mypod, mypod, podKind, mypod.Namespace, mypod.Name, podResource, "status", admission.Update, mynode), + err: "", + }, + { + name: "forbid delete of normal pod status bound to self", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(nil, nil, podKind, mypod.Namespace, mypod.Name, podResource, "status", admission.Delete, mynode), + err: "forbidden: unexpected operation", + }, + + // Normal pods bound to another + { + name: "forbid creating a normal pod bound to another", + podsGetter: noExistingPods, + attributes: admission.NewAttributesRecord(otherpod, nil, podKind, otherpod.Namespace, otherpod.Name, podResource, "", admission.Create, mynode), + err: "can only create mirror pods", + }, + { + name: "forbid update of normal pod bound to another", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(otherpod, otherpod, podKind, otherpod.Namespace, otherpod.Name, podResource, "", admission.Update, mynode), + err: "forbidden: unexpected operation", + }, + { + name: "forbid delete of normal pod bound to another", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(nil, nil, podKind, otherpod.Namespace, otherpod.Name, podResource, "", admission.Delete, mynode), + err: "spec.nodeName set to itself", + }, + { + name: "forbid create of normal pod status bound to another", + podsGetter: noExistingPods, + attributes: admission.NewAttributesRecord(otherpod, nil, podKind, otherpod.Namespace, otherpod.Name, podResource, "status", admission.Create, mynode), + err: "forbidden: unexpected operation", + }, + { + name: "forbid update of normal pod status bound to another", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(otherpod, otherpod, podKind, otherpod.Namespace, otherpod.Name, podResource, "status", admission.Update, mynode), + err: "spec.nodeName set to itself", + }, + { + name: "forbid delete of normal pod status bound to another", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(nil, nil, podKind, otherpod.Namespace, otherpod.Name, podResource, "status", admission.Delete, mynode), + err: "forbidden: unexpected operation", + }, + + // Normal pods not bound to any node + { + name: "forbid creating a normal pod unbound", + podsGetter: noExistingPods, + attributes: admission.NewAttributesRecord(unboundpod, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Create, mynode), + err: "can only create mirror pods", + }, + { + name: "forbid update of normal pod unbound", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(unboundpod, unboundpod, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Update, mynode), + err: "forbidden: unexpected operation", + }, + { + name: "forbid delete of normal pod unbound", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Delete, mynode), + err: "spec.nodeName set to itself", + }, + { + name: "forbid create of normal pod status unbound", + podsGetter: noExistingPods, + attributes: admission.NewAttributesRecord(unboundpod, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "status", admission.Create, mynode), + err: "forbidden: unexpected operation", + }, + { + name: "forbid update of normal pod status unbound", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(unboundpod, unboundpod, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "status", admission.Update, mynode), + err: "spec.nodeName set to itself", + }, + { + name: "forbid delete of normal pod status unbound", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "status", admission.Delete, mynode), + err: "forbidden: unexpected operation", + }, + + // Missing pod + { + name: "forbid delete of unknown pod", + podsGetter: noExistingPods, + attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Delete, mynode), + err: "not found", + }, + + // Resource pods + { + name: "forbid create of pod referencing service account", + podsGetter: noExistingPods, + attributes: admission.NewAttributesRecord(sapod, nil, podKind, sapod.Namespace, sapod.Name, podResource, "", admission.Create, mynode), + err: "reference a service account", + }, + { + name: "forbid create of pod referencing secret", + podsGetter: noExistingPods, + attributes: admission.NewAttributesRecord(secretpod, nil, podKind, secretpod.Namespace, secretpod.Name, podResource, "", admission.Create, mynode), + err: "reference secrets", + }, + { + name: "forbid create of pod referencing configmap", + podsGetter: noExistingPods, + attributes: admission.NewAttributesRecord(configmappod, nil, podKind, configmappod.Namespace, configmappod.Name, podResource, "", admission.Create, mynode), + err: "reference configmaps", + }, + { + name: "forbid create of pod referencing persistentvolumeclaim", + podsGetter: noExistingPods, + attributes: admission.NewAttributesRecord(pvcpod, nil, podKind, pvcpod.Namespace, pvcpod.Name, podResource, "", admission.Create, mynode), + err: "reference persistentvolumeclaims", + }, + + // My node object + { + name: "allow create of my node", + podsGetter: noExistingPods, + attributes: admission.NewAttributesRecord(mynodeObj, nil, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Create, mynode), + err: "", + }, + { + name: "allow update of my node", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(mynodeObj, mynodeObj, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, mynode), + err: "", + }, + { + name: "allow delete of my node", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(nil, nil, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Delete, mynode), + err: "", + }, + { + name: "allow update of my node status", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(mynodeObj, mynodeObj, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "status", admission.Update, mynode), + err: "", + }, + + // Other node object + { + name: "forbid create of other node", + podsGetter: noExistingPods, + attributes: admission.NewAttributesRecord(othernodeObj, nil, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "", admission.Create, mynode), + err: "cannot modify other nodes", + }, + { + name: "forbid update of other node", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(othernodeObj, othernodeObj, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "", admission.Update, mynode), + err: "cannot modify other nodes", + }, + { + name: "forbid delete of other node", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(nil, nil, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "", admission.Delete, mynode), + err: "cannot modify other nodes", + }, + { + name: "forbid update of other node status", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(othernodeObj, othernodeObj, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "status", admission.Update, mynode), + err: "cannot modify other nodes", + }, + + // Unrelated objects + { + name: "allow create of unrelated object", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(&api.ConfigMap{}, nil, configmapKind, "myns", "mycm", configmapResource, "", admission.Create, mynode), + err: "", + }, + { + name: "allow update of unrelated object", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(&api.ConfigMap{}, &api.ConfigMap{}, configmapKind, "myns", "mycm", configmapResource, "", admission.Update, mynode), + err: "", + }, + { + name: "allow delete of unrelated object", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(nil, nil, configmapKind, "myns", "mycm", configmapResource, "", admission.Delete, mynode), + err: "", + }, + + // Unrelated user + { + name: "allow unrelated user creating a normal pod unbound", + podsGetter: noExistingPods, + attributes: admission.NewAttributesRecord(unboundpod, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Create, bob), + err: "", + }, + { + name: "allow unrelated user update of normal pod unbound", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(unboundpod, unboundpod, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Update, bob), + err: "", + }, + { + name: "allow unrelated user delete of normal pod unbound", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Delete, bob), + err: "", + }, + { + name: "allow unrelated user create of normal pod status unbound", + podsGetter: noExistingPods, + attributes: admission.NewAttributesRecord(unboundpod, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "status", admission.Create, bob), + err: "", + }, + { + name: "allow unrelated user update of normal pod status unbound", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(unboundpod, unboundpod, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "status", admission.Update, bob), + err: "", + }, + { + name: "allow unrelated user delete of normal pod status unbound", + podsGetter: existingPods, + attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "status", admission.Delete, bob), + err: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := NewPlugin(nodeidentifier.NewDefaultNodeIdentifier(), tt.strict) + c.podsGetter = tt.podsGetter + err := c.Admit(tt.attributes) + if (err == nil) != (len(tt.err) == 0) { + t.Errorf("nodePlugin.Admit() error = %v, expected %v", err, tt.err) + return + } + if len(tt.err) > 0 && !strings.Contains(err.Error(), tt.err) { + t.Errorf("nodePlugin.Admit() error = %v, expected %v", err, tt.err) + } + }) + } +} From 37cb9c4dc2f338a32b1b5c37f88b6dc811b6dc87 Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Thu, 18 May 2017 14:21:55 -0700 Subject: [PATCH 14/23] fed: Fix bad logic of deletion error handling for federated updater --- federation/pkg/federation-controller/util/federated_updater.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/federation/pkg/federation-controller/util/federated_updater.go b/federation/pkg/federation-controller/util/federated_updater.go index 244fa4c554b..dafb8ab3bd2 100644 --- a/federation/pkg/federation-controller/util/federated_updater.go +++ b/federation/pkg/federation-controller/util/federated_updater.go @@ -122,7 +122,7 @@ func (fu *federatedUpdaterImpl) Update(ops []FederatedOperation) error { fu.recordEvent(op.Obj, eventType, "Deleting", eventArgs...) err = fu.deleteFunction(clientset, op.Obj) // IsNotFound error is fine since that means the object is deleted already. - if err != nil && !errors.IsNotFound(err) { + if errors.IsNotFound(err) { err = nil } } From e9a8af9ac8f0c18e8c324372d1a6acf8913a8837 Mon Sep 17 00:00:00 2001 From: Nikhita Raghunath Date: Fri, 19 May 2017 03:31:27 +0530 Subject: [PATCH 15/23] Add integration test for kube-apiextensions-server. Check if integers are present after decoding. Originally an issue for TPRs: #30213 --- .../test/integration/BUILD | 1 + .../test/integration/basic_test.go | 46 +++++++++++++++++++ .../test/integration/testserver/resources.go | 4 ++ 3 files changed, 51 insertions(+) diff --git a/staging/src/k8s.io/kube-apiextensions-server/test/integration/BUILD b/staging/src/k8s.io/kube-apiextensions-server/test/integration/BUILD index dca4da9927d..ae598a317a8 100644 --- a/staging/src/k8s.io/kube-apiextensions-server/test/integration/BUILD +++ b/staging/src/k8s.io/kube-apiextensions-server/test/integration/BUILD @@ -23,6 +23,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/client-go/dynamic:go_default_library", "//vendor/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/v1alpha1:go_default_library", diff --git a/staging/src/k8s.io/kube-apiextensions-server/test/integration/basic_test.go b/staging/src/k8s.io/kube-apiextensions-server/test/integration/basic_test.go index 659bd4ee717..c176e6da52e 100644 --- a/staging/src/k8s.io/kube-apiextensions-server/test/integration/basic_test.go +++ b/staging/src/k8s.io/kube-apiextensions-server/test/integration/basic_test.go @@ -25,6 +25,7 @@ import ( "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/dynamic" apiextensionsv1alpha1 "k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/v1alpha1" @@ -333,3 +334,48 @@ func TestSelfLink(t *testing.T) { // TODO add test for cluster scoped self-link when its available } + +func TestPreserveInt(t *testing.T) { + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() + if err != nil { + t.Fatal(err) + } + defer close(stopCh) + + noxuDefinition := testserver.NewNoxuCustomResourceDefinition(apiextensionsv1alpha1.ClusterScoped) + noxuVersionClient, err := testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool) + if err != nil { + t.Fatal(err) + } + + ns := "not-the-default" + noxuNamespacedResourceClient := noxuVersionClient.Resource(&metav1.APIResource{ + Name: noxuDefinition.Spec.Names.Plural, + Namespaced: true, + }, ns) + + noxuInstanceToCreate := testserver.NewNoxuInstance(ns, "foo") + createdNoxuInstance, err := noxuNamespacedResourceClient.Create(noxuInstanceToCreate) + if err != nil { + t.Fatal(err) + } + + originalJSON, err := runtime.Encode(unstructured.UnstructuredJSONScheme, createdNoxuInstance) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + gottenNoxuInstance, err := runtime.Decode(unstructured.UnstructuredJSONScheme, originalJSON) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Check if int is preserved. + unstructuredObj := gottenNoxuInstance.(*unstructured.Unstructured).Object + num := unstructuredObj["num"].(map[string]interface{}) + num1 := num["num1"].(int64) + num2 := num["num2"].(int64) + if num1 != 9223372036854775807 || num2 != 1000000 { + t.Errorf("Expected %v, got %v, %v", `9223372036854775807, 1000000`, num1, num2) + } +} diff --git a/staging/src/k8s.io/kube-apiextensions-server/test/integration/testserver/resources.go b/staging/src/k8s.io/kube-apiextensions-server/test/integration/testserver/resources.go index 099c82c1d99..519cb5e9d86 100644 --- a/staging/src/k8s.io/kube-apiextensions-server/test/integration/testserver/resources.go +++ b/staging/src/k8s.io/kube-apiextensions-server/test/integration/testserver/resources.go @@ -59,6 +59,10 @@ func NewNoxuInstance(namespace, name string) *unstructured.Unstructured { "content": map[string]interface{}{ "key": "value", }, + "num": map[string]interface{}{ + "num1": 9223372036854775807, + "num2": 1000000, + }, }, } } From 4d457f55f4b190012440c8a454a2aee338a42c4d Mon Sep 17 00:00:00 2001 From: Shyam Jeedigunta Date: Fri, 19 May 2017 01:11:38 +0200 Subject: [PATCH 16/23] Copy static variable 'verb' before instrumenting APIserver call to prevent overwriting --- .../src/k8s.io/apiserver/pkg/endpoints/metrics/metrics.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/metrics/metrics.go b/staging/src/k8s.io/apiserver/pkg/endpoints/metrics/metrics.go index 863ad98b417..88a363785d6 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/metrics/metrics.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/metrics/metrics.go @@ -103,10 +103,11 @@ func InstrumentRouteFunc(verb, resource string, routeFunc restful.RouteFunction) routeFunc(request, response) + reportedVerb := verb if verb == "LIST" && strings.ToLower(request.QueryParameter("watch")) == "true" { - verb = "WATCH" + reportedVerb = "WATCH" } - Monitor(&verb, &resource, cleanUserAgent(utilnet.GetHTTPClient(request.Request)), rw.Header().Get("Content-Type"), delegate.status, now) + Monitor(&reportedVerb, &resource, cleanUserAgent(utilnet.GetHTTPClient(request.Request)), rw.Header().Get("Content-Type"), delegate.status, now) }) } From 47de152365702c678a6bedb32ef7f471839b1bda Mon Sep 17 00:00:00 2001 From: "Madhusudan.C.S" Date: Thu, 18 May 2017 16:57:44 -0700 Subject: [PATCH 17/23] Log kubefed operations at log level 4 in our test environments. This is useful for debugging test failures that involve federation control plane turn up/down. --- federation/cluster/federation-down.sh | 3 ++- federation/cluster/federation-up.sh | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/federation/cluster/federation-down.sh b/federation/cluster/federation-down.sh index f3f71b2ae26..be91e974f60 100755 --- a/federation/cluster/federation-down.sh +++ b/federation/cluster/federation-down.sh @@ -52,7 +52,8 @@ function unjoin_clusters() { "${context}" \ --federation-system-namespace=${FEDERATION_NAMESPACE} \ --context="${FEDERATION_KUBE_CONTEXT}" \ - --host-cluster-context="${HOST_CLUSTER_CONTEXT}" + --host-cluster-context="${HOST_CLUSTER_CONTEXT}" \ + --v=4 done } diff --git a/federation/cluster/federation-up.sh b/federation/cluster/federation-up.sh index b5c120353a1..62efca9cdfc 100755 --- a/federation/cluster/federation-up.sh +++ b/federation/cluster/federation-up.sh @@ -94,7 +94,8 @@ function init() { --apiserver-enable-basic-auth=true \ --apiserver-enable-token-auth=true \ --apiserver-arg-overrides="--v=4" \ - --controllermanager-arg-overrides="--v=4" + --controllermanager-arg-overrides="--v=4" \ + --v=4 } # join_clusters joins the clusters in the local kubeconfig to federation. The clusters @@ -107,7 +108,8 @@ function join_clusters() { "${context}" \ --federation-system-namespace=${FEDERATION_NAMESPACE} \ --host-cluster-context="${HOST_CLUSTER_CONTEXT}" \ - --context="${FEDERATION_KUBE_CONTEXT}" + --context="${FEDERATION_KUBE_CONTEXT}" \ + --v=4 done } From 20ccdfbd3bcb38fb6a0a2cf6aaa0ec2ea1c92dae Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Fri, 19 May 2017 00:29:39 -0400 Subject: [PATCH 18/23] Fix unbound variable --- hack/local-up-cluster.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/hack/local-up-cluster.sh b/hack/local-up-cluster.sh index 955cb3857e8..1188fee113f 100755 --- a/hack/local-up-cluster.sh +++ b/hack/local-up-cluster.sh @@ -25,6 +25,7 @@ DOCKERIZE_KUBELET=${DOCKERIZE_KUBELET:-""} ALLOW_PRIVILEGED=${ALLOW_PRIVILEGED:-""} ALLOW_SECURITY_CONTEXT=${ALLOW_SECURITY_CONTEXT:-""} PSP_ADMISSION=${PSP_ADMISSION:-""} +NODE_ADMISSION=${NODE_ADMISSION:-""} RUNTIME_CONFIG=${RUNTIME_CONFIG:-""} KUBELET_AUTHORIZATION_WEBHOOK=${KUBELET_AUTHORIZATION_WEBHOOK:-""} KUBELET_AUTHENTICATION_WEBHOOK=${KUBELET_AUTHENTICATION_WEBHOOK:-""} From 250b2299121997a8503013f6a7172079cdcc5731 Mon Sep 17 00:00:00 2001 From: realfake Date: Sat, 22 Apr 2017 22:40:57 +0200 Subject: [PATCH 19/23] Implement providerID node functions for gce *Add splitProviderID helper function *Add getInstanceFromProjectInZoneByName function *Implement gce InstanceTypeByProviderID *Implement gce NodeAddressesByProviderID --- .../providers/gce/gce_instances.go | 67 ++++++++++---- pkg/cloudprovider/providers/gce/gce_test.go | 90 +++++++++++++++++++ pkg/cloudprovider/providers/gce/gce_util.go | 15 ++++ 3 files changed, 156 insertions(+), 16 deletions(-) diff --git a/pkg/cloudprovider/providers/gce/gce_instances.go b/pkg/cloudprovider/providers/gce/gce_instances.go index e6616fed49d..6446f26ff18 100644 --- a/pkg/cloudprovider/providers/gce/gce_instances.go +++ b/pkg/cloudprovider/providers/gce/gce_instances.go @@ -17,7 +17,6 @@ limitations under the License. package gce import ( - "errors" "fmt" "net/http" "strconv" @@ -62,7 +61,27 @@ func (gce *GCECloud) NodeAddresses(_ types.NodeName) ([]v1.NodeAddress, error) { // This method will not be called from the node that is requesting this ID. // i.e. metadata service and other local methods cannot be used here func (gce *GCECloud) NodeAddressesByProviderID(providerID string) ([]v1.NodeAddress, error) { - return []v1.NodeAddress{}, errors.New("unimplemented") + project, zone, name, err := splitProviderID(providerID) + if err != nil { + return []v1.NodeAddress{}, err + } + + instance, err := gce.service.Instances.Get(project, zone, canonicalizeInstanceName(name)).Do() + if err != nil { + return []v1.NodeAddress{}, fmt.Errorf("error while querying for providerID %q: %v", providerID, err) + } + + if len(instance.NetworkInterfaces) < 1 { + return []v1.NodeAddress{}, fmt.Errorf("could not find network interfaces for providerID %q", providerID) + } + networkInterface := instance.NetworkInterfaces[0] + + nodeAddresses := []v1.NodeAddress{{Type: v1.NodeInternalIP, Address: networkInterface.NetworkIP}} + for _, config := range networkInterface.AccessConfigs { + nodeAddresses = append(nodeAddresses, v1.NodeAddress{Type: v1.NodeExternalIP, Address: config.NatIP}) + } + + return nodeAddresses, nil } // InstanceTypeByProviderID returns the cloudprovider instance type of the node @@ -70,7 +89,15 @@ func (gce *GCECloud) NodeAddressesByProviderID(providerID string) ([]v1.NodeAddr // node that is requesting this ID. i.e. metadata service and other local // methods cannot be used here func (gce *GCECloud) InstanceTypeByProviderID(providerID string) (string, error) { - return "", errors.New("unimplemented") + project, zone, name, err := splitProviderID(providerID) + if err != nil { + return "", err + } + instance, err := gce.getInstanceFromProjectInZoneByName(project, zone, name) + if err != nil { + return "", err + } + return instance.Type, nil } // ExternalID returns the cloud provider ID of the node with the specified NodeName (deprecated). @@ -339,30 +366,38 @@ func (gce *GCECloud) getInstancesByNames(names []string) ([]*gceInstance, error) func (gce *GCECloud) getInstanceByName(name string) (*gceInstance, error) { // Avoid changing behaviour when not managing multiple zones for _, zone := range gce.managedZones { - name = canonicalizeInstanceName(name) - mc := newInstancesMetricContext("get", zone) - res, err := gce.service.Instances.Get(gce.projectID, zone, name).Do() - mc.Observe(err) + instance, err := gce.getInstanceFromProjectInZoneByName(gce.projectID, zone, name) if err != nil { - glog.Errorf("getInstanceByName: failed to get instance %s; err: %v", name, err) - if isHTTPErrorCode(err, http.StatusNotFound) { continue } return nil, err } - return &gceInstance{ - Zone: lastComponent(res.Zone), - Name: res.Name, - ID: res.Id, - Disks: res.Disks, - Type: lastComponent(res.MachineType), - }, nil + return instance, nil } return nil, cloudprovider.InstanceNotFound } +func (gce *GCECloud) getInstanceFromProjectInZoneByName(project, zone, name string) (*gceInstance, error) { + name = canonicalizeInstanceName(name) + mc := newInstancesMetricContext("get", zone) + res, err := gce.service.Instances.Get(project, zone, name).Do() + mc.Observe(err) + if err != nil { + glog.Errorf("getInstanceFromProjectInZoneByName: failed to get instance %s; err: %v", name, err) + return nil, err + } + + return &gceInstance{ + Zone: lastComponent(res.Zone), + Name: res.Name, + ID: res.Id, + Disks: res.Disks, + Type: lastComponent(res.MachineType), + }, nil +} + func getInstanceIDViaMetadata() (string, error) { result, err := metadata.Get("instance/hostname") if err != nil { diff --git a/pkg/cloudprovider/providers/gce/gce_test.go b/pkg/cloudprovider/providers/gce/gce_test.go index 5bba03bf97c..c05f0bdccbe 100644 --- a/pkg/cloudprovider/providers/gce/gce_test.go +++ b/pkg/cloudprovider/providers/gce/gce_test.go @@ -158,3 +158,93 @@ func TestCreateFirewallFails(t *testing.T) { t.Errorf("error expected when creating firewall without any tags found") } } + +func TestSplitProviderID(t *testing.T) { + providers := []struct { + providerID string + + project string + zone string + instance string + + fail bool + }{ + { + providerID: ProviderName + "://project-example-164317/us-central1-f/kubernetes-node-fhx1", + project: "project-example-164317", + zone: "us-central1-f", + instance: "kubernetes-node-fhx1", + fail: false, + }, + { + providerID: ProviderName + "://project-example.164317/us-central1-f/kubernetes-node-fhx1", + project: "project-example.164317", + zone: "us-central1-f", + instance: "kubernetes-node-fhx1", + fail: false, + }, + { + providerID: ProviderName + "://project-example-164317/us-central1-fkubernetes-node-fhx1", + project: "", + zone: "", + instance: "", + fail: true, + }, + { + providerID: ProviderName + ":/project-example-164317/us-central1-f/kubernetes-node-fhx1", + project: "", + zone: "", + instance: "", + fail: true, + }, + { + providerID: "aws://project-example-164317/us-central1-f/kubernetes-node-fhx1", + project: "", + zone: "", + instance: "", + fail: true, + }, + { + providerID: ProviderName + "://project-example-164317/us-central1-f/kubernetes-node-fhx1/", + project: "", + zone: "", + instance: "", + fail: true, + }, + { + providerID: ProviderName + "://project-example.164317//kubernetes-node-fhx1", + project: "", + zone: "", + instance: "", + fail: true, + }, + { + providerID: ProviderName + "://project-example.164317/kubernetes-node-fhx1", + project: "", + zone: "", + instance: "", + fail: true, + }, + } + + for _, test := range providers { + project, zone, instance, err := splitProviderID(test.providerID) + if (err != nil) != test.fail { + t.Errorf("Expected to failt=%t, with pattern %v", test.fail, test) + } + + if test.fail { + continue + } + + if project != test.project { + t.Errorf("Expected %v, but got %v", test.project, project) + } + if zone != test.zone { + t.Errorf("Expected %v, but got %v", test.zone, zone) + } + if instance != test.instance { + t.Errorf("Expected %v, but got %v", test.instance, instance) + } + } +} diff --git a/pkg/cloudprovider/providers/gce/gce_util.go b/pkg/cloudprovider/providers/gce/gce_util.go index 2748d7c0dd5..8f59cd5c495 100644 --- a/pkg/cloudprovider/providers/gce/gce_util.go +++ b/pkg/cloudprovider/providers/gce/gce_util.go @@ -17,7 +17,9 @@ limitations under the License. package gce import ( + "errors" "fmt" + "regexp" "strings" "k8s.io/apimachinery/pkg/types" @@ -35,6 +37,8 @@ type gceInstance struct { Type string } +var providerIdRE = regexp.MustCompile(`^` + ProviderName + `://([^/]+)/([^/]+)/([^/]+)$`) + func getProjectAndZone() (string, string, error) { result, err := metadata.Get("instance/zone") if err != nil { @@ -100,3 +104,14 @@ func isHTTPErrorCode(err error, code int) bool { apiErr, ok := err.(*googleapi.Error) return ok && apiErr.Code == code } + +// splitProviderID splits a provider's id into core components. +// A providerID is build out of '${ProviderName}://${project-id}/${zone}/${instance-name}' +// See cloudprovider.GetInstanceProviderID. +func splitProviderID(providerID string) (project, zone, instance string, err error) { + matches := providerIdRE.FindStringSubmatch(providerID) + if len(matches) != 4 { + return "", "", "", errors.New("error splitting providerID") + } + return matches[1], matches[2], matches[3], nil +} From c0feba4f9f4fb176dd0b69297bf3bc2192beeaaf Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Fri, 19 May 2017 16:47:15 +0800 Subject: [PATCH 20/23] remove unreachable code Signed-off-by: Ma Shimiao --- .../pkg/apiserver/customresource_handler.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/customresource_handler.go b/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/customresource_handler.go index d8723e69126..6bad06de2be 100644 --- a/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/customresource_handler.go +++ b/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/customresource_handler.go @@ -104,13 +104,11 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { if !ok { // programmer error panic("missing context") - return } requestInfo, ok := apirequest.RequestInfoFrom(ctx) if !ok { // programmer error panic("missing requestInfo") - return } if !requestInfo.IsResourceRequest { pathParts := splitPath(requestInfo.Path) From bad2b97dc77209a30ee7327803495c2d97a13225 Mon Sep 17 00:00:00 2001 From: Michail Kargakis Date: Fri, 19 May 2017 12:18:55 +0200 Subject: [PATCH 21/23] Redirect kubeadm issues to kubeadm repo Signed-off-by: Michail Kargakis --- .github/ISSUE_TEMPLATE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index b0ee15def24..e48e95f33bc 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -7,6 +7,7 @@ | Component | Repo | | --------- | ------------------------------------------------------------------ | | kubectl | [kubernetes/kubectl](https://github.com/kubernetes/kubectl/issues/new) | +| kubeadm | [kubernetes/kubeadm](https://github.com/kubernetes/kubeadm/issues/new) | **What keywords did you search in Kubernetes issues before filing this one?** (If you have found any duplicates, you should instead reply there.): From d2529bb6b64daeaeae751dd9402ef780b4fcea97 Mon Sep 17 00:00:00 2001 From: Wojciech Tyczynski Date: Fri, 19 May 2017 13:57:36 +0200 Subject: [PATCH 22/23] Avoid sleep in endpoint controller --- pkg/controller/endpoint/endpoints_controller.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/controller/endpoint/endpoints_controller.go b/pkg/controller/endpoint/endpoints_controller.go index d24bc6130d6..df5db13e6f2 100644 --- a/pkg/controller/endpoint/endpoints_controller.go +++ b/pkg/controller/endpoint/endpoints_controller.go @@ -146,7 +146,6 @@ func (e *EndpointController) Run(workers int, stopCh <-chan struct{}) { go func() { defer utilruntime.HandleCrash() - time.Sleep(5 * time.Minute) // give time for our cache to fill e.checkLeftoverEndpoints() }() From 2f4cb6bfe7cb3324c6b4eb6dad2d4f2964d5ca88 Mon Sep 17 00:00:00 2001 From: Marcin Wielgus Date: Fri, 19 May 2017 14:50:55 +0200 Subject: [PATCH 23/23] Use integer comparisons instead of string comparisons in autoscaler config validation --- cluster/gce/util.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cluster/gce/util.sh b/cluster/gce/util.sh index 547f0085f95..a6d497b7306 100755 --- a/cluster/gce/util.sh +++ b/cluster/gce/util.sh @@ -1203,14 +1203,14 @@ function create-cluster-autoscaler-mig-config() { # Each MIG must have at least one node, so the min number of nodes # must be greater or equal to the number of migs. - if [[ ${AUTOSCALER_MIN_NODES} < ${NUM_MIGS} ]]; then + if [[ ${AUTOSCALER_MIN_NODES} -lt ${NUM_MIGS} ]]; then echo "AUTOSCALER_MIN_NODES must be greater or equal ${NUM_MIGS}" exit 2 fi # Each MIG must have at least one node, so the min number of nodes # must be greater or equal to the number of migs. - if [[ ${AUTOSCALER_MAX_NODES} < ${NUM_MIGS} ]]; then + if [[ ${AUTOSCALER_MAX_NODES} -lt ${NUM_MIGS} ]]; then echo "AUTOSCALER_MAX_NODES must be greater or equal ${NUM_MIGS}" exit 2 fi